JavaScript Variables & Data Types Master Guide
Understanding JavaScript Variables
Variables in JavaScript are named storage locations for data - you give a value a name so you can reference and work with it throughout your code. What makes JavaScript variables different from variables in languages like Java or C is that they're dynamically typed: you don't declare a type when creating a variable, and the same variable can hold a string, then a number, then a boolean, in sequence, without any complaint from the interpreter. This flexibility is convenient and occasionally the source of confusing bugs, which is part of why understanding how JavaScript handles types is worth spending time on early.
Key Characteristics of JavaScript Variables
Dynamic typing- Variables have no fixed type - the type is determined by whatever value they currently hold and can change at runtime.Case sensitive- myVar and myvar are completely different variables. JavaScript is strict about this.Loosely typed- JavaScript will often convert between types automatically when operations require it - this is called coercion, and it has rules worth learning.
Variable Declaration in JavaScript
JavaScript has three keywords for declaring variables: var, let, and const. In modern JavaScript you should use const by default, let when you need to reassign, and essentially never use var. Var is function-scoped, gets hoisted in ways that produce surprising behavior, and can be redeclared without error - all properties that make code harder to reason about. Let and const are block-scoped, which means they exist only within the curly braces they're declared in, which is the behavior you almost always want.
1// VAR - function scoped, avoid in modern code
2var firstName = "John";
3var age = 30;
4
5// LET - block scoped, use when value will change
6let lastName = "Doe";
7let score = 95.5;
8let isOnline = false;
9
10// CONST - block scoped, cannot be reassigned
11const PI = 3.14159;
12const API_KEY = "abc123def456";
13const MAX_USERS = 1000;
14
15// Reassignment: let works, const throws
16let counter = 0;
17counter = 1; // Fine
18
19const TAX_RATE = 0.07;
20// TAX_RATE = 0.08; // TypeError: Assignment to constant variable
21
22// Multiple declarations on one line
23let x = 1, y = 2, z = 3;
24const DEFAULT_COLOR = "blue", FALLBACK_COLOR = "red";
25
26// Declared without value - both start as undefined
27let uninitializedVar;
28var oldStyleVar;
29// const mustInitialize; // SyntaxError - const requires initializationDeclaration Methods Comparison
var- Function-scoped, hoisted and initialized to undefined, can be redeclared. Avoid in modern code.let- Block-scoped, hoisted but not initialized (causes ReferenceError if accessed before declaration), cannot be redeclared in the same scope.const- Block-scoped, cannot be reassigned after initialization, must be initialized when declared. Use this by default.
JavaScript Naming Conventions and Rules
Variable names must start with a letter, underscore, or dollar sign - not a number, not a hyphen, not a space. After the first character you can use letters, numbers, underscores, and dollar signs. Reserved words like let, const, function, and class can't be variable names. The conventions: camelCase for regular variables and functions, SCREAMING_SNAKE_CASE for constants, and descriptive enough names that the code reads like sentences rather than abbreviations. The abbreviated version feels faster to type but will cost more in confusion six months from now.
1// Valid variable names
2let userName = "Alice";
3let _privateVar = "internal";
4let $element = document.getElementById('main');
5let user2 = "secondary";
6let camelCaseVariable = "standard";
7let CONSTANT_VALUE = "screaming snake case for constants";
8
9// Invalid - will throw SyntaxError
10// let 2ndUser = "invalid"; // Cannot start with number
11// let user-name = "invalid"; // Hyphens not allowed
12// let let = "invalid"; // Reserved keyword
13// let user name = "invalid"; // Spaces not allowed
14
15// Descriptive names vs not
16let elapsedTimeInSeconds = 3600; // Clear
17let et = 3600; // Ambiguous
18
19let isDataValid = true; // Clearly a boolean
20let dataCheck = true; // Type is not obvious from the name
21
22// Constants should name the value's meaning
23const MAX_RETRY_ATTEMPTS = 3;
24let isLoading = false;
25let itemPrice = 19.99;
26let hasUserPermission = true;
27let apiEndpoint = "/api/users";
28
29// These all work but communicate nothing
30let a = "Alice";
31let x1 = 42;
32let flag = true;
33let temp = "temporary";
34// avoid names like theseNaming Rules and Conventions
Must start with- A letter (a-z, A-Z), underscore, or dollar sign. Numbers as the first character are a syntax error.Can contain- Letters, numbers, underscores, and dollar signs anywhere after the first character.Case sensitivity- myVariable, myvariable, and MyVariable are three different variables.Reserved words- Identifiers like let, const, function, class, return, and many others are reserved and cannot be used as variable names.camelCase- Standard convention for variables and functions: firstName, calculateTotal, isUserLoggedIn.UPPER_SNAKE_CASE- Convention for module-level constants: MAX_SIZE, API_URL, DEFAULT_TIMEOUT.
JavaScript Primitive Data Types
JavaScript has seven primitive data types. Primitives are immutable - when you appear to change them you're actually creating a new value, not modifying the existing one. The seven are: number (all numeric values, integers and floats alike), string (text), boolean (true or false), undefined (declared but not assigned), null (explicitly empty), symbol (unique identifiers, added in ES6), and bigint (integers beyond the safe integer range, added in ES2020). The typeof operator returns a string naming the type of any value, with one famous exception: typeof null returns 'object', which is a historical bug that's never been fixed.
1// Number - integers and floats share one type
2let integer = 42;
3let float = 3.14159;
4let scientific = 2.5e-4; // 0.00025
5let hex = 0xFF; // 255
6let infinity = Infinity;
7let notANumber = NaN;
8
9// String
10let singleQuote = 'Hello';
11let doubleQuote = "World";
12let backtick = `Template Literal`;
13let escaped = "Line 1\nLine 2";
14
15// Boolean
16let isActive = true;
17let isEmpty = false;
18let isGreater = 5 > 3; // true
19
20// Undefined - declared, not assigned
21let undefinedVar;
22console.log(undefinedVar); // undefined
23
24// Null - explicitly empty
25let emptyValue = null;
26
27// Symbol - each one is unique
28let sym1 = Symbol('description');
29let sym2 = Symbol('description');
30console.log(sym1 === sym2); // false
31
32// BigInt - for integers beyond Number.MAX_SAFE_INTEGER
33let bigNumber = 9007199254740991n;
34let huge = 1234567890123456789012345678901234567890n;
35
36// typeof results
37console.log(typeof 42); // "number"
38console.log(typeof "hello"); // "string"
39console.log(typeof true); // "boolean"
40console.log(typeof undefined); // "undefined"
41console.log(typeof null); // "object" - historical bug, not a real object
42console.log(typeof Symbol()); // "symbol"
43console.log(typeof 123n); // "bigint"Number Data Type in Depth
JavaScript uses a single Number type for everything numeric - integers and floats alike, all stored as 64-bit floating-point values following the IEEE 754 standard. This means 42 and 42.0 are the same thing, which is convenient, but it also means decimal arithmetic has precision limits that catch people off guard. The classic example is 0.1 + 0.2, which doesn't equal 0.3 in JavaScript - it equals 0.30000000000000004. This isn't a JavaScript bug specifically, it's a consequence of how binary floating-point works, and the same issue exists in most programming languages.
1// Number representations
2let decimal = 42;
3let floating = 3.14159;
4let negative = -15.5;
5let scientific = 1.23e6; // 1,230,000
6let hex = 0x2A; // 42 in hexadecimal
7let octal = 0o52; // 42 in octal
8let binary = 0b101010; // 42 in binary
9
10// Special values
11let infinity = Infinity;
12let negativeInfinity = -Infinity;
13let notANumber = NaN; // Result of invalid math, e.g. 0/0
14
15// IEEE 754 floating-point precision issue
16console.log(0.1 + 0.2); // 0.30000000000000004
17console.log(0.1 + 0.2 === 0.3); // false - classic gotcha
18
19// Number methods
20let num = 123.456;
21console.log(num.toFixed(2)); // "123.46"
22console.log(num.toPrecision(5)); // "123.46"
23console.log(num.toString(16)); // hex representation
24
25// Number limits
26console.log(Number.MAX_VALUE); // 1.7976931348623157e+308
27console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
28console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
29
30// Type checking
31console.log(Number.isInteger(42)); // true
32console.log(Number.isInteger(42.5)); // false
33console.log(Number.isSafeInteger(9007199254740991)); // true
34console.log(Number.isNaN(NaN)); // true
35console.log(isNaN("hello")); // true (global isNaN coerces first)
36
37// Arithmetic
38let a = 10, b = 3;
39console.log(a + b); // 13
40console.log(a - b); // 7
41console.log(a * b); // 30
42console.log(a / b); // 3.3333...
43console.log(a % b); // 1 (remainder)
44console.log(a ** b); // 1000 (exponentiation, ES2016)
45
46// Increment/decrement
47let count = 5;
48count++; // post-increment: returns 5, then becomes 6
49++count; // pre-increment: becomes 7, then returns 7
50count--; // post-decrementString Data Type Comprehensive Guide
Strings represent text data and are immutable - once created, you can't change the characters in a string, only create new strings from operations on it. You can use single quotes, double quotes, or backticks, and the choice between single and double is mostly stylistic. Backtick strings (template literals) are different: they support embedded expressions with dollar sign and curly braces, span multiple lines without escape sequences, and are generally the better choice for any string that includes variable content.
1// Three ways to create strings
2let single = 'Single quoted string';
3let double = "Double quoted string";
4let backticks = `Template literal string`;
5
6// Template literals - embedding expressions
7let name = "Alice";
8let age = 25;
9let greeting = `Hello, ${name}! You are ${age} years old.`;
10// "Hello, Alice! You are 25 years old."
11
12// Multi-line (template literals make this clean)
13let multiLine = `This is
14a multi-line
15string`;
16
17// Escape sequences
18let newLine = "Line 1\nLine 2";
19let tab = "Column1\tColumn2";
20let quote = "He said, \"Hello!\"";
21let backslash = "Path: C:\\Program Files\\";
22let unicode = "Smiley: \u{1F600}";
23
24// String properties and methods
25let text = "JavaScript Programming";
26
27console.log(text.length); // 22
28console.log(text[0]); // "J" - zero-indexed
29console.log(text.charAt(4)); // "S"
30
31console.log(text.toUpperCase()); // "JAVASCRIPT PROGRAMMING"
32console.log(text.toLowerCase()); // "javascript programming"
33
34console.log(text.indexOf("Script")); // 4
35console.log(text.includes("Java")); // true
36console.log(text.startsWith("Java")); // true
37console.log(text.endsWith("ing")); // true
38
39console.log(text.slice(0, 10)); // "JavaScript"
40console.log(text.replace("Java", "Type")); // "TypeScript Programming"
41console.log(" hello ".trim()); // "hello"
42console.log("hello".padStart(8, "*")); // "***hello"
43console.log("hello".padEnd(8, "*")); // "hello***"
44console.log("hello".repeat(3)); // "hellohellohello"
45
46// Split and join
47let tags = "js,html,css";
48console.log(tags.split(",")); // ["js", "html", "css"]
49console.log(["js", "html", "css"].join(" | ")); // "js | html | css"Boolean and Logical Operations
Booleans are true or false, simple in concept but with some JavaScript-specific nuances worth knowing. Every value in JavaScript is either truthy or falsy - meaning it evaluates to true or false when used in a boolean context like an if statement. The falsy values are: false, 0, empty string, null, undefined, and NaN. Everything else is truthy, including empty arrays and empty objects, which trips people up. The nullish coalescing operator (??) and optional chaining (?.) were added in ES2020 and solve two very common patterns in cleaner ways than the old alternatives.
1// Boolean values
2let isActive = true;
3let isEmpty = false;
4
5// Boolean coercion - what evaluates as true/false
6Boolean("hello") // true
7Boolean("") // false
8Boolean(42) // true
9Boolean(0) // false
10Boolean({}) // true - empty object is truthy
11Boolean([]) // true - empty array is truthy
12Boolean(null) // false
13Boolean(undefined) // false
14Boolean(NaN) // false
15
16// Comparison operators
17let equal = (5 == 5); // true
18let strictEqual = (5 === '5'); // false - different types
19let notEqual = (5 != 3); // true
20let strictNotEqual = (5 !== '5'); // true
21let greaterThan = (10 > 5); // true
22let lessOrEqual = (10 <= 5); // false
23
24// Logical operators
25let andResult = (true && false); // false
26let orResult = (true || false); // true
27let notResult = (!true); // false
28
29// Short-circuit evaluation
30let user = { name: "John" };
31let displayName = user && user.name; // "John" - only evaluates right side if left is truthy
32let defaultName = user.name || "Guest"; // "John" - uses right side if left is falsy
33
34// Ternary operator
35let score = 85;
36let status = (score >= 60) ? "Pass" : "Fail"; // "Pass"
37let message = (score >= 90) ? "Excellent" : "Good";
38
39// Nullish coalescing (ES2020) - only falls back on null/undefined, not 0 or ""
40let userName = null;
41let displayUser = userName ?? "Anonymous"; // "Anonymous"
42
43let count = 0;
44let displayCount = count ?? "Not set"; // 0 - not "Not set", because 0 is not null/undefined
45// With ||: count || "Not set" would give "Not set" - different behavior
46
47// Optional chaining (ES2020) - short-circuits on null/undefined
48let userProfile = {
49 personal: {
50 name: "Alice"
51 }
52};
53let city = userProfile?.personal?.address?.city; // undefined - no error despite missing properties
54let name = userProfile?.personal?.name; // "Alice"Special Primitive Types: undefined, null, Symbol, BigInt
These four primitives each fill a specific role. Undefined is what you get when a variable is declared but not assigned a value, or when a function doesn't return anything explicitly - it's the language's way of saying this has no value yet. Null is a value you assign intentionally to indicate emptiness - the difference is philosophical but matters in practice. Symbol creates values that are guaranteed unique even if they have the same description, useful for object property keys that shouldn't collide. BigInt handles integers beyond the safe integer range and uses an n suffix.
1// Undefined - variable declared but not assigned
2let undefinedVar;
3console.log(undefinedVar); // undefined
4console.log(typeof undefinedVar); // "undefined"
5
6// Functions without a return statement
7function noReturn() {
8 // no return
9}
10console.log(noReturn()); // undefined
11
12// Null - intentionally empty
13let emptyValue = null;
14console.log(emptyValue); // null
15console.log(typeof emptyValue); // "object" - historical bug in JavaScript
16
17// Symbol - each one is unique
18let sym1 = Symbol("id");
19let sym2 = Symbol("id");
20console.log(sym1 === sym2); // false - same description, different symbol
21
22// Symbol as object property key
23const ID_KEY = Symbol("id");
24let user = {
25 name: "John",
26 [ID_KEY]: 123, // Won't collide with any string key
27};
28console.log(user[ID_KEY]); // 123
29
30// BigInt - for large integers
31let big1 = 9007199254740991n; // n suffix
32let big2 = BigInt("123456789012345678901234567890");
33
34console.log(big1 + 1n); // 9007199254740992n
35console.log(big1 * 2n); // 18014398509481982n
36// console.log(big1 + 1); // TypeError - cannot mix BigInt and Number
37
38// BigInt vs Number comparisons
39console.log(1n === 1); // false - different types
40console.log(1n == 1); // true - value equality with coercion
41console.log(1n < 2); // true
42
43// Checking for null and undefined
44let value = null;
45console.log(value === null); // true
46console.log(value === undefined); // false
47console.log(value == null); // true - == null matches both null and undefined
48console.log(value == undefined); // true - same reasonType Conversion and Coercion in JavaScript
JavaScript converts between types in two ways: explicitly, when you call Number(), String(), or Boolean(), and implicitly, when operators work on operands of different types. The implicit version - coercion - is where JavaScript's reputation for surprising behavior comes from. The string plus operator adds two values but if either is a string it concatenates instead of adding, so "5" + 2 gives you "52" not 7. The minus operator has no string behavior so "5" - 2 gives 3. The rules are consistent but learning them takes a bit of time. Using strict equality (===) instead of loose equality (==) eliminates one major source of coercion surprises.
1// Explicit conversion
2let stringToNumber = Number("42"); // 42
3let numberToString = String(42); // "42"
4let boolToString = String(true); // "true"
5let stringToBool = Boolean("hello"); // true
6
7// Parsing - stops at first non-numeric character
8let intParse = parseInt("42px"); // 42
9let floatParse = parseFloat("3.14cm"); // 3.14
10let intBase = parseInt("1010", 2); // 10 (binary to decimal)
11
12// Implicit coercion - where things get surprising
13let coerced1 = "5" + 2; // "52" - + with a string concatenates
14let coerced2 = "5" - 2; // 3 - - has no string behavior, coerces to number
15let coerced3 = "5" * "2"; // 10 - both coerced to number
16let coerced4 = !!"hello"; // true - double negation to boolean
17
18// Unary plus: shortest way to convert to number
19let num1 = +"123"; // 123
20let num2 = +true; // 1
21let num3 = +false; // 0
22let num4 = +null; // 0
23let num5 = +undefined; // NaN
24let num6 = +""; // 0
25
26// String conversion
27let str1 = 123 + ""; // "123" - concatenation with empty string
28let str2 = true + ""; // "true"
29let str3 = null + ""; // "null"
30
31// Boolean conversion
32let bool1 = !!0; // false
33let bool2 = !!1; // true
34let bool3 = !!""; // false
35let bool4 = !!"text"; // true
36let bool5 = !!null; // false
37let bool7 = !!{}; // true - empty object is truthy
38
39// Loose equality performs coercion before comparing
40console.log(5 == "5"); // true
41console.log(true == 1); // true
42console.log(false == 0); // true
43console.log(null == undefined); // true
44console.log(null == 0); // false - null only loosely equals undefined
45
46// Strict equality: no coercion
47console.log(5 === "5"); // false
48console.log(true === 1); // false
49console.log(null === undefined); // false
50
51// Use explicit conversion in real code
52let userInput = "42";
53let numericValue = Number(userInput); // Clear intent
54// let numericValue = userInput * 1; // Works but reads as an accidentVariable Scope and Hoisting in JavaScript
Scope controls which parts of your code can access a variable. JavaScript has three kinds: global scope (accessible everywhere), function scope (accessible within a function), and block scope (accessible within the curly braces it's declared in). Var uses function scope, let and const use block scope, which is another reason to prefer them. Hoisting is the behavior where JavaScript moves declarations to the top of their scope during compilation - var declarations are hoisted and initialized to undefined, let and const declarations are hoisted but not initialized, which means accessing them before their declaration throws a ReferenceError instead of silently returning undefined.
1// Global scope
2let globalVar = "accessible everywhere";
3
4function demonstrateScope() {
5 let functionVar = "only inside this function";
6
7 if (true) {
8 let blockVar = "only inside this block";
9 const blockConst = "also block-scoped";
10 var functionScopedVar = "function-scoped despite being inside if block";
11
12 console.log(globalVar); // Accessible
13 console.log(functionVar); // Accessible
14 console.log(blockVar); // Accessible
15 }
16
17 // console.log(blockVar); // ReferenceError
18 console.log(functionScopedVar); // Accessible - var ignores block boundaries
19}
20
21// var hoisting: declaration is moved up, initialized to undefined
22console.log(hoistedVar); // undefined (not a ReferenceError)
23var hoistedVar = "I'm hoisted";
24
25// let hoisting: declaration moved up but NOT initialized - Temporal Dead Zone
26// console.log(hoistedLet); // ReferenceError: Cannot access before initialization
27let hoistedLet = "I'm in temporal dead zone until this line";
28
29// Function declaration hoisting - the whole function is hoisted
30hoistedFunction(); // Works
31
32function hoistedFunction() {
33 console.log("Function declarations are fully hoisted!");
34}
35
36// Function expressions are not hoisted
37// constFunction(); // TypeError
38const constFunction = function() {
39 console.log("Not hoisted");
40};
41
42// Closures - inner functions retain access to outer variables
43function outerFunction() {
44 let outerVar = "I'm in the outer function";
45
46 function innerFunction() {
47 console.log(outerVar); // Accessible through closure
48 }
49
50 return innerFunction;
51}
52
53const closureExample = outerFunction();
54closureExample(); // "I'm in the outer function"Constants with Objects and Arrays
This is the const behavior that catches people: const prevents you from reassigning the variable, but it doesn't make the value immutable. If the value is an object or array, you can still add, change, and delete properties or elements. What you can't do is replace the whole thing. So const person = {name: 'John'} lets you do person.name = 'Jane', but not person = {name: 'Jane'}. If you actually want the object to be immutable, Object.freeze does that - though only shallowly, meaning nested objects aren't frozen.
1// const with primitives: truly immutable
2const PI = 3.14159;
3const MAX_SIZE = 100;
4// PI = 3.14; // TypeError: Assignment to constant variable
5
6// const with objects: the reference is constant, the object is not
7const person = {
8 name: "John",
9 age: 30
10};
11
12person.age = 31; // Fine - modifying a property
13person.city = "New York"; // Fine - adding a property
14delete person.age; // Fine - removing a property
15// person = { name: "Jane" }; // TypeError - reassigning the variable
16
17// const with arrays: same story
18const colors = ["red", "green", "blue"];
19
20colors.push("yellow"); // Fine
21colors[0] = "orange"; // Fine
22colors.pop(); // Fine
23// colors = ["purple", "pink"]; // TypeError
24
25// Object.freeze makes the object itself immutable (shallowly)
26const immutablePerson = Object.freeze({
27 name: "John",
28 age: 30,
29 address: {
30 city: "Boston"
31 }
32});
33
34// immutablePerson.age = 31; // Silently fails in non-strict, throws in strict mode
35// immutablePerson.newProp = "value"; // Same
36
37// But nested objects are not frozen
38immutablePerson.address.city = "Chicago"; // This works - freeze is shallow
39
40// Deep freeze if you need full immutability
41function deepFreeze(obj) {
42 Object.freeze(obj);
43 Object.getOwnPropertyNames(obj).forEach(prop => {
44 if (obj[prop] !== null &&
45 typeof obj[prop] === 'object' &&
46 !Object.isFrozen(obj[prop])) {
47 deepFreeze(obj[prop]);
48 }
49 });
50 return obj;
51}
52
53// const in for...of loops: a new const binding per iteration
54const USERS = ["Alice", "Bob", "Charlie"];
55for (const user of USERS) {
56 console.log(user); // Each iteration gets its own binding
57}
58
59// for loop counter needs let, not const
60for (let i = 0; i < 5; i++) { }
61// for (const i = 0; i < 5; i++) {} // TypeError on i++JavaScript Variables Best Practices
The practices that make the biggest difference over time: use const by default and only switch to let when you have a specific reason to reassign; write names that read as self-documentation rather than abbreviations that need decoding; avoid var entirely; initialize variables when you declare them rather than declaring first and assigning later; and prefer explicit type conversions over relying on coercion to do what you expect. These aren't rules with exceptions - they're defaults that occasionally get overridden for good reasons.
1// const by default, let when reassignment is needed
2const DEFAULT_TIMEOUT = 5000;
3let currentUser = null;
4// var anything = ...; // just don't
5
6// Descriptive names
7let userCount = 0; // Clear
8let uc = 0; // Saves 8 characters, costs readability
9
10// Initialize at declaration
11let username = "";
12let emailAddress = null; // Explicit null signals intentional absence
13// let emailAddress; // Implicit undefined is less clear
14
15// Group related values into objects
16const API_CONFIG = {
17 baseUrl: "https://api.example.com",
18 timeout: 5000,
19 retries: 3
20};
21
22// Name magic numbers
23const TAX_RATE = 0.07;
24let total = subtotal * TAX_RATE; // The name explains the number
25// let total = subtotal * 0.07; // Where did 0.07 come from?
26
27// Use proper types
28let isActive = true; // Boolean
29let isActive = 1; // Number masquerading as boolean - confusing
30
31// Explicit type conversion
32let pageSize = Number(userInput);
33// let pageSize = userInput * 1; // Works but looks accidental
34
35// Template literals for strings with variables
36let welcomeMessage = `Hello ${userName}, you have ${unreadCount} new messages.`;
37
38// Declare variables close to where they're used
39function calculateTotal(items) {
40 const TAX_RATE = 0.07;
41 let subtotal = 0;
42
43 for (let item of items) {
44 subtotal += item.price;
45 }
46
47 return subtotal * (1 + TAX_RATE);
48}
49
50// Use destructuring for cleaner assignment
51const [first, second] = [1, 2, 3];
52const { name, age } = person;
53
54// Keep global state minimal
55(function() {
56 let localVar = "scoped to this IIFE, not global";
57})();