A program is a machine for turning values into other values, so the first thing worth knowing is what a value is and what the machine will do with one.
This course assumes nothing. It is worked in JavaScript, chosen because it runs everywhere without a toolchain: every example here can be pasted into a browser's developer console, or saved into a file and run with node file.js or bun file.js. The language is the vehicle. Numbers, names, types and the traps in them are the same in Python, Java, Go and Rust, and where JavaScript is unusual this course says so rather than letting you learn a local habit as a universal law.
Expressions, and the thing they leave behind
Type 3 + 4 into a console and it answers 7. That is the whole model in miniature. 3 + 4 is an expression: a piece of program text that can be evaluated. Evaluating it produces a value, 7, and the original text is then finished with. Values are what actually exist while a program runs. Expressions are only the notation you use to ask for them.
Expressions nest, and evaluation works inside out. In 2 * (3 + 4), the inner expression 3 + 4 is evaluated first to 7, and the outer one becomes 2 * 7, which evaluates to 14. Precedence decides the shape when brackets are absent: 2 + 3 * 4 is 14, not 20, because * binds tighter than +. This is the arithmetic convention and not a programming invention, but the consequences are sharper here, since a program will not tell you it read your line differently from how you wrote it. When in doubt, bracket it. Nobody has ever been confused by a redundant bracket.
console.log(...) prints a value where you can see it, and it appears in almost every example below. It is worth separating the two ideas now, because they are confused constantly by beginners: computing a value and displaying one are different acts. 3 + 4 produces 7 whether or not anybody looks. console.log(3 + 4) produces 7 and then also puts it on the screen. A program that computes correctly and prints nothing is still correct; it is merely useless. That distinction returns with force in the lesson on functions.
Numbers, all one kind
JavaScript has one numeric type. 7, 7.0, -3, 2.5 and 6.02e23 are all the same kind of thing, a number, and there is no separate integer type as there is in most languages. Every one of them is a 64 bit binary floating point value, the format standardised as IEEE 754 in 1985 and implemented in the hardware of every general purpose processor sold since.
The usual operators are +, -, *, / and %. Division always produces a number rather than truncating: 7 / 2 is 3.5, which differs from Python 2, C and Java, where dividing two integers throws the fraction away. The remainder operator % gives what is left after division, so 17 % 5 is 2, and it is the standard tool for asking whether one number divides another. Exponentiation is **, so 2 ** 10 is 1024.
Three values live in the number type that are not numbers in any ordinary sense. Infinity is what 1 / 0 gives, rather than an error. -Infinity is the other end. NaN, short for not a number, is what an arithmetic operation gives when there is no sensible answer: 0 / 0, or Math.sqrt(-1), or the result of trying to read a number out of the text "hello". NaN is contagious, in that any arithmetic touching it produces NaN again, so a single bad reading early in a calculation can silently poison every number downstream. It has one famous property: NaN === NaN is false, which is why the test for it is Number.isNaN(x) and never x === NaN.
Because a number carries 53 bits of significance, whole numbers are exact only up to , which is 9007199254740991 and is available as Number.MAX_SAFE_INTEGER. Above that, the spacing between representable values exceeds 1 and integers start to be skipped: 9007199254740992 + 1 evaluates to 9007199254740992. Database identifiers routinely exceed that range, which is why they usually arrive as strings.
Strings hold text
A string is a sequence of characters, written between single quotes, double quotes or backticks: 'hello', "hello", `hello`. The three are interchangeable for plain text, and the reason to prefer one is practical. Use whichever quote is not inside the text, so "it's fine" needs no escaping, and use backticks when you want to interpolate, which is what `Total: ${count}` does: the expression inside ${...} is evaluated and its value dropped into the text.
Strings know their length, "hello".length is 5, and support the usual operations by method call: "hello".toUpperCase() gives "HELLO", "hello".slice(1, 3) gives "el", " x ".trim() gives "x". None of these change the original. Strings in JavaScript are immutable, so every one of those methods returns a new string and leaves the old one untouched, which is why s.toUpperCase() on its own does nothing useful: you have to keep the result.
The one thing to hold on to is that "7" and 7 are different values of different types that print almost identically. That single fact is behind most of the confusion in the next section.
Booleans, and the two kinds of nothing
A boolean is true or false, and it is the type that comparison produces. 3 < 4 evaluates to true; "a" === "b" evaluates to false. Booleans are the entire subject of the next lesson, so here they are just another kind of value: they can be stored in a name, printed, and passed around like any number.
Two further values exist to mean absence, and they are not the same. undefined is what you get when nothing was ever supplied: a name declared but not assigned, a function parameter left off at the call, a property that was never set. null is a value someone chose deliberately to mean "no value here". The rough division is that undefined is the machine saying nothing was there, and null is a programmer saying nothing is meant to be there.
Both are worth naming early because they are the leading cause of a program stopping mid-run, which is the subject of the errors lesson. And JavaScript has a famous wart here: typeof null returns "object", not "null". It is a bug from the language's first implementation in 1995 that was never fixable without breaking existing pages, and it is preserved in the standard. Memorise it, because it will otherwise cost you an hour one day.
Names hold values so a later line can use them
A name is bound to a value with const or let:
const price = 40;
let count = 3;
count = count + 1;
console.log(price * count);
// 160Read = as "becomes", never as "equals". The right side is evaluated first, all the way down to a value, and that value is then bound to the name on the left. So count = count + 1 is not an equation with no solution: it evaluates count + 1, which is 4, and rebinds count to it.
const and let differ in exactly one respect: a const name cannot be rebound afterwards, and attempting it is an error. let can be rebound as often as you like. The working habit worth forming is to reach for const first and change it to let only when you find yourself needing to reassign, because a name that never changes is one less thing to track when you are reading the code back at midnight. You will also meet var, the older declaration form, whose scoping rules are strange enough that current practice is simply not to use it.
The name is not the value. Two names can hold the same number without being connected in any way:
let a = 5;
let b = a;
a = 99;
console.log(b);
// 5b was bound to the value that a held at that moment, which was 5, and rebinding a afterwards does nothing to b. That is obvious for numbers, and this course returns to it in the lesson on references, where it stops being obvious for arrays and objects.
Example. What does this print, and why?
let x = 2;
let y = x * 3;
x = 10;
console.log(x + y);Line by line. x is bound to 2. Then x * 3 is evaluated with the current value of x, giving 6, and y is bound to 6. Then x is rebound to 10; y is unaffected, because it holds a value and not a link to x. So x + y is 10 + 6, and the program prints 16.
Now you. What does this print?
let p = 4;
let q = p + 1;
p = q * 2;
console.log(p - q);Answer
p is 4, so q becomes 5. Then p is rebound to q * 2, which is 10. So p - q is 10 - 5, and it prints 5.
A type decides what an operator means
Every value has a type, and the type is what decides what an operator does to it. + is the clearest case, because it means two unrelated things:
console.log(2 + 3); // 5
console.log("2" + "3"); // "23"Addition on numbers, concatenation on strings. Now the awkward part. When the two sides disagree, JavaScript does not stop. It coerces one side to match the other and carries on, and the rule for + is that if either side is a string, the other becomes a string too:
console.log("5" + 3); // "53"
console.log("5" - 3); // 2- has no string meaning, so there the coercion goes the other way and the string becomes a number. The same line of code, one character apart, converts in opposite directions. This is the single most productive source of beginner bugs in the language, and it is worth being precise about why: numbers arriving from outside a program, from a form field, a text file, a command line argument, are strings. "5" + 3 is what a total looks like when someone forgot to convert.
Convert deliberately. Number("12.5") gives 12.5, and Number("abc") gives NaN, which is at least a value you can test for with Number.isNaN. String(12.5) gives "12.5". parseInt("12abc") gives 12, stopping at the first character it cannot use, which is occasionally what you want and more often hides a problem you would rather have seen. Check the type of anything you are unsure about with typeof, which returns a string: typeof 7 is "number", typeof "7" is "string", typeof true is "boolean".
Be clear about the limit of this section. Coercion is JavaScript's, not programming's. Python raises an error on "5" + 3; Java will not compile it. The transferable lesson is not the table of conversions but the habit underneath it: know the type of every value you handle, and convert at the point where data enters your program rather than discovering the type halfway through a calculation.
Example. A form field yields the string "20" and a discount is 5. What does "20" - 5 give, and what does "20" + 5 give?
- has no meaning for strings, so "20" is coerced to the number 20 and the result is the number 15. + sees a string on the left, so 5 is coerced to "5" and the result is the string "205". Written properly, Number("20") + 5 gives 25, and there is no ambiguity left to reason about.
Now you. What are the value and the type of each of "3" * "4", "3" + 4 and 1 + 2 + "3"?
Answer
"3" * "4" is the number 12: * has no string meaning, so both sides become numbers. "3" + 4 is the string "34". 1 + 2 + "3" is the string "33", because + groups left to right: 1 + 2 is evaluated first to the number 3, and only then does 3 + "3" concatenate.
Why 0.1 + 0.2 is not 0.3
Run it and see:
console.log(0.1 + 0.2);
// 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);
// falseThis is not a JavaScript defect, and the same line gives the same answer in Python, Java, C and Go. It follows from storing numbers in binary with a fixed number of bits, and the argument is short enough to give in full.
In base ten, a fraction has a terminating expansion exactly when its denominator has no prime factors besides 2 and 5. One third does not, so it is written as 0.333... and any decimal you actually write down is an approximation. Binary has only the factor 2 available, so the terminating fractions are those whose denominators are powers of two: a half, a quarter, three eighths. One tenth is not one of them, since 10 has a factor of 5. In binary, 0.1 is 0.000110011001100... with 1100 repeating forever.
A double stores 53 significant bits, so that expansion is cut off and rounded to the nearest representable value. The number that gets stored is exactly , which you can verify: in a console, 3602879701896397 / 2 ** 55 === 0.1 returns true. As a decimal that is 0.1000000000000000055511151231257827..., slightly above a tenth. The stored value of 0.2 is likewise slightly above two tenths. Their sum, rounded again to 53 bits, lands on a value whose shortest decimal representation is 0.30000000000000004, while the nearest double to 0.3 is a shade below three tenths. Two different doubles, so === says false, correctly.
The practical consequences are three. First, never compare computed floating point values with ===. Compare with a tolerance: Math.abs(a - b) < 1e-9. Second, never store money as a fraction of a unit. Store integer pence or cents and divide only when you print, which is what every accounting system does. Third, expect the error to accumulate: adding 0.1 to itself ten times does not give 1, and a loop that steps by 0.1 will not land on its endpoint.
Example. A program computes 0.1 * 3 and needs to know whether the result is 0.3. What should it do?
0.1 * 3 evaluates to 0.30000000000000004, so === 0.3 is false and would report a correct calculation as wrong. Compare within a tolerance instead: Math.abs(0.1 * 3 - 0.3) < 1e-9 is true. The actual gap is about , so any tolerance from about up to the precision the problem cares about will do.
Now you. Five items at £19.99 are totalled as 19.99 * 5, which prints 99.94999999999999. Why, and what should be stored instead?
Answer
19.99 is not exactly representable in binary, so the stored value is a hair off a hundredth, and multiplying by 5 multiplies that error by 5 too, leaving it large enough to show in the shortest decimal that identifies the result. Store the price as the integer 1999 pence, multiply to get 9995, and divide by 100 only at the moment of printing.
What a program cannot yet do
You can now compute: values of three useful types, names to hold intermediate results, and honest knowledge of where the arithmetic is approximate. What you cannot yet do is have the program behave differently on different input. Every program written with only these pieces runs the same lines in the same order and produces the same output every time.
To do more, a program has to ask a question about a value and act on the answer. Comparison already produces the right kind of value for that, a boolean, and the next lesson is about turning one into a fork in the road.