JavaScript Basics #2 Variables and Types
We set up the environment in #1 Getting Started and Setup, so now we’ll dive into the language itself. This post covers how to declare variables, what types JavaScript has, and what to watch out for when moving between them.
Declaring variables — let and const
#
The two keywords for declaring variables in JavaScript are let and const.
let count = 0; // value can change later
count = count + 1; // OK
const name = 'Curtis'; // can't change once set
name = 'another name'; // ✗ TypeError
The rule is simple — default to const, use let only when reassignment is needed. It can feel awkward at first, but once you’re used to it, the intent of your code becomes much clearer. You can see at a glance which values won’t change.
Where did var go?
#
Old JavaScript had a keyword called var. It still works today, but new code rarely uses it. var has these pitfalls:
- Function-scoped — ignores blocks (
if,for) - Hoisted with
undefinedinitialization — accessing it before declaration doesn’t throw - Allows redeclaration with the same name — accident-prone
let/const cleaned all of that up. This series barely uses var.
The 8 primitive types #
Every value in JavaScript belongs to one of the following 8 types.
| Type | Examples | Category |
|---|---|---|
string | 'hello', "world", `tpl` | primitive |
number | 42, 3.14, -1 | primitive |
boolean | true, false | primitive |
null | null (intentionally empty) | primitive |
undefined | undefined (no value yet) | primitive |
bigint | 9007199254740993n | primitive |
symbol | Symbol('id') | primitive |
object | {}, [], functions, class instances | reference |
The first 7 are primitive types, and only object is a reference type. This split is the starting point for understanding everything JavaScript does.
Type checking — typeof
#
typeof 'hello'; // 'string'
typeof 42; // 'number'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof null; // 'object' ← JavaScript's famous bug
typeof {}; // 'object'
typeof []; // 'object' ← arrays are also objects
typeof function(){}; // 'function' ← actually object, but special-cased
typeof null === 'object' is an early-language bug that got locked in. Compatibility means it can’t be fixed. To check for null, compare directly with value === null.
The difference between null and undefined
#
Both express “no value”, but the intent is slightly different.
undefined— value is not yet set. JavaScript fills it in automatically.null— value is intentionally empty. The developer writes it explicitly.
let x; // declared but no value → undefined
console.log(x); // undefined
const obj = { name: 'Curtis' };
console.log(obj.age); // undefined (missing property)
function f(arg) {
return arg;
}
f(); // undefined (no argument)
let user = null; // not logged in yet
// ... later
user = { id: 'u1', name: 'Curtis' };In practice, function return values and API responses use both. Treat them as semantically equivalent for “a value that may or may not be present”. Comparing with == catches both (next section).
Primitive vs reference — the most important distinction in JavaScript #
This is where beginners get stuck most often.
Primitive types are copied by value.
let a = 10;
let b = a; // copy a's value (10) into b
b = 20;
console.log(a); // 10 (unaffected)
console.log(b); // 20
Reference types copy “where it points to”, not the value.
const obj1 = { count: 10 };
const obj2 = obj1; // now points to the same object
obj2.count = 20;
console.log(obj1.count); // 20 ← obj1 changed too
console.log(obj2.count); // 20
obj1 and obj2 are two names pointing to the same object. Change the object through one, and the change shows up through the other. This distinction is the root of countless “why did this also change?” puzzles you’ll hit while working with JavaScript.
When you really want to copy an object #
const original = { name: 'Curtis', age: 30 };
const copy = { ...original };
copy.age = 31;
console.log(original.age); // 30 (unaffected)
console.log(copy.age); // 31
{...original} is a shallow copy that copies only the top-level properties. Nested objects are still shared by reference. Deep copying comes in the next series.
Type conversion — a place full of traps #
JavaScript tends to convert types automatically, which makes it easy to get unexpected results.
Intentional conversion #
Number('42'); // 42
Number('hello'); // NaN (Not a Number)
String(42); // '42'
Boolean(0); // false
Boolean(1); // true
Boolean(''); // false
Boolean('false'); // true ← the string 'false' is non-empty
Memorize the 7 falsy values that Boolean converts to false.
Boolean(false); // false
Boolean(0); // false
Boolean(-0); // false
Boolean(0n); // false (bigint 0)
Boolean(''); // false
Boolean(null); // false
Boolean(undefined); // false
Boolean(NaN); // false
Everything else is truthy. The empty object {} and empty array [] are also truthy, which often surprises people.
Boolean({}); // true
Boolean([]); // true
Boolean('0'); // true ← the string '0' is non-empty
Boolean('false'); // true
Pitfalls of automatic conversion #
There are cases where operators silently convert types.
'5' + 3; // '53' ← string concatenation
'5' - 3; // 2 ← numeric operation
'5' * '2'; // 10 ← both converted to number
[] + []; // ''
[] + {}; // '[object Object]'
+ is the most confusing. If either side is a string it concatenates; otherwise it adds. When in doubt, do an explicit conversion with Number() or String().
== vs === — always use ===
#
There are two comparison operators.
// Loose comparison — converts then compares
'5' == 5; // true ← string converted to number
0 == false; // true
null == undefined; // true
// Strict comparison — types must also match
'5' === 5; // false
0 === false; // false
null === undefined;// false
Almost always use ===. Automatic conversion often goes against intuition and causes bugs. The value == null pattern (equivalent to null || undefined) is the only reasonable use of == — for checking both null and undefined at once.
Scope of let and const
#
Finally, the block scope semantics that let/const introduce.
if (true) {
const message = 'hi';
console.log(message); // hi
}
console.log(message); // ✗ ReferenceError — invisible outside the block
Variables declared inside { and } aren’t visible outside that block. This applies to for loops, if branches, and function bodies alike. As a result, the reach of a variable is narrower and easier to track.
Wrap-up #
What this post covered:
- Default to
const, useletonly when reassignment is needed.varis rarely used in new code. - 8 types — string/number/boolean/null/undefined/bigint/symbol/object
- The
typeof null === 'object'pitfall — compare directly withvalue === null - Primitives copy by value, objects copy by reference.
{...obj}for a shallow copy. - 7 falsy values: false, 0, -0, 0n, ‘’, null, undefined, NaN
- Always use
===instead of== let/constare block-scoped
In the next post (#3 Control Flow) we’ll cover branches and loops like if/while/for/switch, plus modern syntax (for...of, for...in, switch fallthrough pitfalls).