jsguides

JavaScript Fundamentals: Variables and Types

Every JavaScript program starts with data. Understanding JavaScript fundamentals like variables and types is the foundation every developer needs before building anything useful. This tutorial covers how to store and manipulate information: var, let, const, and all seven primitive types.

Declaring Variables

JavaScript gives you three ways to declare variables: var, let, and const. Each serves a different purpose.

let — block-scoped variables

The modern way to declare variables that change:

let score = 0;
score = score + 10;  // Reassignment works
console.log(score);  // Output: 10

Variables declared with let are scoped to the block where they’re defined. This scoping rule is one of the main reasons let replaced var in modern JavaScript. A let variable declared inside an if block, loop, or any pair of curly braces cannot leak into the surrounding code. Trying to access it outside causes a ReferenceError, which catches accidental reuse early instead of silently producing wrong results:

if (true) {
  let message = "Hello from inside";
  console.log(message);  // Works here
}
console.log(message);  // ReferenceError: message is not defined

const — constants that don’t change

Use const for values that won’t be reassigned:

const API_URL = "https://api.example.com";
const MAX_RETRIES = 3;

// This throws an error:
// MAX_RETRIES = 5;  // TypeError: Assignment to constant variable

Objects and arrays declared with const can still be modified. The const keyword prevents reassignment of the binding, not mutation of the value. For objects and arrays, this means you can push to the array or set object properties freely, because those operations change the contents without replacing the whole reference. Understanding this distinction avoids a common confusion when newcomers first encounter const with non-primitive values:

const user = { name: "Alice" };
user.name = "Bob";  // This works - we're mutating the object
// user = {};       // This throws - can't reassign

var — the old way

Avoid var in modern code. It has function scope (not block scope) and hoisting behavior that causes confusing bugs:

function oldStyle() {
  if (true) {
    var secret = "I'm hoisted to function scope";
  }
  console.log(secret);  // Works - var ignores blocks
}

Rule of thumb: Use const by default. Use let when you need to reassign. Never use var.

Primitive Types

JavaScript has seven primitive types.

string

Textual data enclosed in quotes:

let greeting = "Hello, World!";
let name = 'Alice';
let template = `Welcome, ${name}!`;  // Template literal
console.log(template);  // Output: Welcome, Alice!

number

All numbers are floating-point (no separate integer type):

let age = 25;
let price = 19.99;
let negative = -10;
let scientific = 2.5e6;  // 2,500,000

Special values: Infinity, -Infinity, and NaN (Not a Number). These follow IEEE 754 floating-point semantics. Infinity results from overflow or division by zero, while NaN appears when a math operation produces a non-numeric result, such as taking the square root of a negative value. One important quirk: NaN !== NaN is true, so use Number.isNaN() to check for it:

console.log(10 / 0);       // Infinity
console.log(Math.sqrt(-1)); // NaN

boolean

True or false values:

let isActive = true;
let isComplete = false;

// Boolean context examples
if (isActive) {
  console.log("Running");
}

undefined

A variable declared but not assigned:

let notAssigned;
console.log(notAssigned);  // undefined

null

Intentional absence of value:

let user = null;  // Explicitly set to nothing

symbol

Unique identifiers (often used as object keys):

const id = Symbol("user-id");
const anotherId = Symbol("user-id");
console.log(id === anotherId);  // false - each is unique

bigint

For integers larger than 2^53 - 1:

const bigNumber = 9007199254740991n;
const calculated = bigNumber + 1n;

The typeof Operator

Check a value’s type at runtime:

typeof "hello"     // "string"
typeof 42          // "number"
typeof true        // "boolean"
typeof undefined   // "undefined"
typeof Symbol("x") // "symbol"
typeof 123n       // "bigint"
typeof {}          // "object"
typeof []          // "object"  - arrays are objects!
typeof null        // "object"  - historical bug, still exists

Type Coercion

JavaScript automatically converts types in certain situations:

String Coercion

The + operator triggers string concatenation when either operand is a string, while other arithmetic operators convert strings to numbers:

"5" + 3    // "53" - number becomes string
"5" - 3    // 2    - string becomes number

This difference between + and - trips up beginners regularly — + prefers strings, but -, *, and / all coerce to numbers.

Boolean Coercion

Falsy values: false, 0, "", null, undefined, NaN. Every other value is truthy, including empty objects and arrays. This matters most in conditional contexts:

if ("") {
  console.log("truthy");
} else {
  console.log("falsy");  // This runs
}

Boolean(1)   // true
Boolean(0)   // false
Boolean("x") // true

Equality Comparisons

Use === (strict) instead of == (loose):

5 === "5"   // false - different types
5 == "5"    // true - type coercion happens

null == undefined  // true
null === undefined // false

Best practice: Always use === to avoid surprising behavior.

Working with Numbers

Math Operations

The Math object provides static methods for common arithmetic. These all return numbers and handle edge cases according to IEEE 754 rules:

Math.floor(4.9);   // 4
Math.ceil(4.1);    // 5
Math.round(4.5);   // 5
Math.abs(-5);      // 5
Math.max(1, 5, 3); // 5
Math.min(1, 5, 3); // 1

When you have string values that represent numbers, such as form inputs or query parameters, you need to convert them before you can use them in calculations. JavaScript gives you several parsing functions, each with different behavior around invalid characters:

Parsing strings to numbers

parseInt("42");       // 42
parseFloat("3.14");   // 3.14
Number("42");         // 42
+"42";                // 42 (unary plus)

parseInt("42px");     // 42 - parses until invalid char
Number("42px");       // NaN - entire string must be valid

Working with Strings

Common Methods

String methods return new strings rather than mutating the original, since strings are immutable in JavaScript. Here are the ones you will reach for most often:

let text = "JavaScript";

// Case
text.toUpperCase();   // "JAVASCRIPT"
text.toLowerCase();   // "javascript"

// Search
text.indexOf("Script");    // 4
text.includes("Java");    // true
text.startsWith("Java");  // true

// Modify
text.replace("Java", "Type");  // "TypeScript"
text.trim();                    // removes whitespace
text.split("");                 // ["J","a","v","a","S","c","r","i","p","t"]

Template literals go beyond simple string building. They let you embed expressions, span multiple lines without escape characters, and keep the code readable even when the output is complex. Use backticks instead of quotes:

Template Literals

const name = "Alice";
const age = 30;

const intro = `My name is ${name} and I am ${age} years old.`;
console.log(intro);  // My name is Alice and I am 30 years old.

// Multi-line
const html = `
  <div>
    <h1>${name}</h1>
  </div>
`;

Why variables come first

Variables are the bridge between raw values and real programs. A constant number on its own is not very interesting, but a named variable can represent a score, a balance, a setting, or a user input that changes over time. Once you start naming values, your code becomes easier to read because each identifier explains what the value means in the current context.

That naming step also shapes how you think about the problem. Instead of tracking a pile of literals, you start working with concepts such as price, count, message, or isLoggedIn. This is why variable declarations are usually the first building block people learn after syntax basics.

Let, const, and var in practice

The choice between const, let, and var affects both readability and behavior. const is the default for values that should not be reassigned, which makes the intent obvious to the next person reading the code. let works well when the value changes during a loop, a calculation, or a user flow. var is mostly a legacy option now, and its broader scope can create bugs that are hard to trace.

In practice, the safest habit is to start with const and only move to let when reassignment is part of the design. That pattern keeps accidental changes from sneaking in and makes it much easier to scan a file for the values that are expected to move.

Reading types at runtime

JavaScript often needs to check a value while the program is already running. That is where typeof becomes useful. It lets you confirm whether you are dealing with a string, number, boolean, or another primitive before you apply an operation that depends on that type. The result is especially helpful when data comes from an API, a form field, or any place that can return mixed values.

One thing to remember is that typeof is a quick check, not a full type system. Arrays and plain objects both report as "object", and null has that historical quirk too. Even so, it is still a practical first step when you need to guard a calculation or decide which parsing path to follow.

Coercion is a feature and a risk

JavaScript’s coercion rules make it easy to combine values without a lot of ceremony. A string can become a number, a number can become a string, and booleans can be treated as truthy or falsy in conditions. That flexibility is convenient, but it also means the same expression can succeed for a reason you did not expect.

The safest approach is to be explicit when the program depends on a particular type. Use === for comparisons, parse strings before arithmetic, and keep a close eye on places where a value might come from outside your code. Once you know where coercion is happening, you can decide whether it is helping or hiding a mistake.

Prefer names that describe intent

Good variable names do more than label a value. They tell the reader why the value exists and how long it should matter. A name like cartTotal or isFormValid gives immediate context, while a vague name forces the reader to inspect the surrounding code. That extra clarity helps when you return to the file later, because you can recover the meaning of the code without reverse engineering it from scratch.

Type checks support safer branching

When a value can arrive from user input, storage, or an external API, it is worth checking the type before you use it. A short guard can prevent a confusing runtime path and make the rest of the function simpler. That is especially useful in JavaScript, where values sometimes arrive in string form even when they represent numbers or booleans. A little checking up front makes the rest of the code more predictable.

Summary

You now understand:

  • let for variables that change
  • const for constants and objects you won’t reassign
  • Seven primitive types: string, number, boolean, undefined, null, symbol, bigint
  • Type coercion and why === matters
  • How to work with numbers and strings

In the next tutorial, we’ll build on this foundation and explore functions and scope.

Next steps

See Also