People who don't write code might think it's magic, but it's just language, and logic. Anyone can pick it up, because our species excels at both language and logic. So let's do this!
If you feel like I skipped some crucial information, maybe try part 0 of "How to Code: Fundamentals".
This Lesson
First you'll learn about control flow and scope. Then you'll learn about variables, a little bit about data types, and then functions.
Control flow is a programming concept that means you are in control of which instructions get done in what order. In this lesson we'll mostly be focused on the simplest control flow scheme: line 1 happens, then line 2 happens, then line 3, etc. until the program is complete. I'll use the if
statement to demonstrate a common control flow feature of programming.
Variables are nametags for pieces of data. There are many types of data. Variables are not "typed" in JavaScript, meaning a variable can point to data of one type one moment, and another type of data the next.
Functions are nametags for code, but they're also more. Functions take input and send output, and can be any level of complexity, making them entire mini-programs within your program.
Control Flow
"Control flow" refers to things you can do in programming that control which instructions get sent to the CPU in what order. Take your simplest code sample, a traditional Hello World in browser-side JavaScript:
const text = 'Hello World';
alert(text);
It has two lines of code:
- The first line of code creates a
const
-- a "constant", which is a type of variable. The const
named text
will hold the text, "Hello World".
- The second line of code calls a function that's built into JavaScript,
alert
. It passes input to that function by placing the variable name text
between the parentheses.
alert
is a well-known function that pops up a simple message box on the web browser. What should the message say? That's the input you provide when you call the alert
function. More on functions later. The upshot for now is that, when you run this code, a pop-up, saying "Hello World", should appear.
But how does the alert
function know anything about a variable named text
? That's thanks to control flow, and scope, a second feature under your control, which will be explained soon. Suffice to say for now: control flow goes from top to bottom, line by line. Since the definition of text
came before the call to alert
, text
existed when alert
was called.
To explain scope better I'll introduce the if
statement. This code contains a common mistake:
let color = 'blue';
if (color === 'blue') {
const text = 'Hello World';
}
alert(text);
What's happening here? First, let me explain operators. A single =
equals sign is known as the "assignment operator". It assigns a value on the right hand side, to a variable on the left. So on the first line of code above, a variable called color
is being assigned the value "blue".
But on the next line of code, we have three equals signs ===
back to back. This is one of the many comparison operators. The ===
operator means "is exactly equal to".
And of course, on that second line of code above, we've met our first if
statement. If the conditions in the parentheses evaluate to true
, then the code in the curly braces will be executed. This is a control flow feature. If the code above was run with no modifications, the following line of code would always be executed because color
will always be exactly equal to "blue" (since we just defined it as such in the line above):
const text = 'Hello World';
But since that's all the code in the curly braces after the if
conditional… what happens next? After any control flow structure in your code has met its end, control flow always resumes its previous state, meaning in this case, we go back to doing things line-by-line, top to bottom…
Scope
…Which would be great if not for the bug. It's a very common bug: a variable is being used "out of scope".

Let's look at the whole thing again:
let color = 'blue';
if (color === 'blue') {
const text = 'Hello World';
}
alert(text);
If you ran it in a browser, no pop-up box would appear, and you'd be told Uncaught ReferenceError: text is not defined
in the Developer Tools console. (To open the console, right click almost anywhere in the page, and choose Inspect -- e.g. Inspect element.)
So why isn't text
defined? It's because it fell out of scope. Basically, each set of curly braces creates a context one level "inner" compared to the context of where you typed those new curly braces. Using our example, the const text = 'Hello World';
line is in a context one level "inner" vs everything outside those curly braces. Each of these contexts has its own scope.
Variables from "inner" levels of scope can't be used in "outer" levels of scope. Variables from "outer" levels of scope can be used in the "inner" scope code. Just remember, in any given situation, the so-called "outer scope" is often an "inner scope" compared to the overall global scope, that is, the outermost part of the control flow. The variable will fall out of scope when the block that created it, ends. So long as that doesn't happen before control flow reaches the part of the code where you want to use the variable -- the variable will be available.
In the example code, the if
block ended with the closing curly brace }
and so the const text
no longer existed when alert
was called. That's scope.
Variables & Data Types
Variables (aka vars) are nametags for data. This alone is a powerful feature. Combining variables with other variables, and combining variables with functions, represents almost 100% of programming, so this section is critical.
Someone recently asked in the JavaScript Mastery discord why variables were important, so here's a bullet list of reasons off-the-cuff:
- Configuration (the easiest config system for any piece of software is a list of config vars at the top)
- Software Speed (when the same value is referenced multiple times, a variable is faster)
- Function Arguments (without which functions would be far less powerful)
- Readability (even if you're the sole maintainer of a program, you'll thank yourself later if your code is readable)
- Debugging Steps (easier to find the mistake in your code if that mistake leaves a named imprint you can output)
First thing you need to know is the three types of variable, identified by the keyword used to declare them: const
, let
, and var
.
const
- The variable must be defined. Can't be changed. Normal scope rules apply.
let
- The variable need not be defined. Can be changed. Normal scope rules apply.
var
- The variable need not be defined. Can be changed. Special scope rules apply.
Declaring & Defining Variables
In JavaScript, as with most languages, you have to declare a variable before you can use it. You declare a variable by issuing one of the above three keywords, plus the variable's name.
let foo;
var bar;
You can go one step further in the same line, defining the variable by using the assignment operator and some value on the right of that.
let foo = 2;
var bar = 'The quick brown fox jumped over the lazy dog.';
Variables made with const
must always be declared and defined at the same time.
const foobar = 'Two quick brown foxes jumped over three lazy dogs.';
Why var
is Useless
So what are these "special scope rules" attached to var
? I'll borrow the example from MDN:
var x = 1;
if (x === 1) {
var x = 2;
console.log(x);
}
console.log(x);
The special scope rule is that var
does nothing special. You still have to declare the variable in the outside scope, and even the second var
in the if
block (var x = 2;
) offers no special features. Compare the above, to the below:
let x = 1;
if (x === 1) {
x = 2;
console.log(x);
}
console.log(x);
They're practically identical (the second example is shorter by one keyword) and they get the same result. For this reason, JavaScript coders tend to drift toward never using var
. Just remember: const
for constants that can't be changed, let
for everything else.
In all cases, you need to remember to declare your variables in proper scope, so that the variable will be available when it's needed, and released from memory when it's not needed anymore.
Data Types
Variables in JavaScript both do, and don't, have a "type". Every piece of data has a "data type", and yet, variables are not "strongly typed" in JavaScript -- meaning any variable can point to any type of data -- no variable is locked into the type of data it was first assigned. Still, data types are important; eventually, you will find yourself often querying the data type of a variable as part of an if
or switch
statement, to make more powerful functions. Long story short, we need a quick word on data types.
For a beginner, here's what you need to know about types. There are two categories.
Category 1: "primitives"
- Data type
boolean
stores either true
or false
- Data type
string
stores text of whatever length
- Data type
number
will be reported for every number you'll ever use
null
and undefined
data types, explained in the next lesson
Category 2: "objects"
- variables created using the
let obj = new className()
syntax
- variables created using the
let obj = { field: 'value', property: 1 }
syntax
- arrays, regular expressions, etc
Why care? Because category 2, the objects, are always handled "by reference". This means if you assign it to another variable, both variables point to the same object, as shown here:
let foo = { field: 'value', property: 1 };
const bar = foo;
bar.property = 2;
console.log(foo.property);
Only the object types do this.

Functions
Functions give nametags to code, but also much more. Functions can take input, and can send output, like little mini-programs. Most of the best functions are small, single-task functions.
Let's take a look at a simple function that has both input and output:
function add(a, b) {
return a + b;
}
The function is called add
and it adds two numbers, that are given to it when it's called (the inputs). It adds the two numbers, returning (outputting) the result.
Let's break this down. Obviously function add
creates a function called add. The (a, b)
is where things get interesting. This is called the function's "signature". This is how you define the inputs for the function. In this example, we're defining two variables, a
and b
, that will be available inside the function. The values of a
and b
depend on whoever writes code "calling" this function. (You "call" a function by typing its name with a set of parentheses after it.) For example, if someone writes:
let c = add(6, 18);
…then, inside the function, a
will equal 6 and b
will equal 18. Notice that the person calling the function used the same number of inputs as the signature specified.
And the last thing to understand about the function definition for add
, is the line starting with return
. The return
statement is a function's output. If the function is used thusly: let c = add(6, 18);
…you might notice the let c =
part and wonder, how does it know what c
should equal? It knows because of the return
statement inside the function. This example, our add
function, doesn't even create a variable to return -- it just returns the direct result of a math operation. Outputting values of whatever complexity in this way is fine.

Let's look at a slightly more complex example. Let's say you have a simple project for logging and storing log entries.
const ERR_LOGSTOR = 'Invalid storage medium';
const logStorage = [];
function storeLog(logMessage='Message not specified') {
if (Array.isArray(logStorage)) {
logStorage.push(logMessage);
return 0;
}
else {
return ERR_LOGSTOR;
}
function logMessage(logMessage=undefined) {
if (logMessage === undefined) {
console.log('An undefined message was not logged');
}
else {
console.log(logMessage);
const status = storeLog(logMessage);
if (status === ERR_LOGSTOR) {
console.warn('Log storage error: ' + status);
}
}
}
logMessage('System started');
Let's break that down:
const ERR_LOGSTOR = 'Invalid storage medium';
Whenever you see two slashes //
it means that the rest of that line is a "comment". This is how programmers put documentation into the code itself. Code can also be "commented out" by putting two slashes at the beginning of the line. Code that's been "commented out" is completely ignored when you run the program.
const logStorage = [];
Two square brackets []
means "create a new, empty array". So when we begin this program, logStorage
will be assigned an empty array.
function storeLog(logMessage='Message not specified') {
This is the function declaration and the beginning of its definition. The signature is one value this time, but the value has a default. Since the value has a default, you can skip that part of the signature when calling the function: it could be storeLog('I am a log message')
or it could be storeLog()
with nothing between the parentheses. If nothing is between the parentheses, the function will proceed as if you had typed storeLog('Message not specified')
.
if (Array.isArray(logStorage)) {
logStorage.push(logMessage);
return 0;
}
else {
return ERR_LOGSTOR;
}
In the above snippet, first we do a sanity check -- meaning we check the code's situation to avoid producing an error. In this case, that means making sure the logStorage
variable is an Array. If it is, we add the logMessage
to the end of the logStorage
array and output a 0 as our function's return value. If logStorage
is not an Array, we do nothing except return the error message defined at the top of the script.
A quick additional note about return
: using return
also exits control flow from the function, in addition to sending output. Notice you can have multiple return
statements in a function. Control flow will only arrive at one of them, but you can manage this with features like the if
statement.
}
This ends the function. Every code block begins with {
and ends with }
. This applies to control flow structures such as the if
statement, as well as functions, classes, etc.
function logMessage(logMessage=undefined) {
Once again we have a function declaration with a single parameter as its signature, and once again the parameter has a default value.
if (logMessage === undefined) {
console.log('An undefined message was not logged');
}
Here we're doing some error trapping: we don't want to log nothing, but we do want to log the fact that nothing was logged. (Ah, business logic.)
else {
console.log(logMessage);
Whenever you use if
, you can use else
right after the if
block ends. The if
block executes if the condition in parentheses is true; the else
block runs otherwise.
const status = storeLog(logMessage);
Here we're using the previous function, storeLog
. Yes, you can use functions within functions. If doing task A means also doing task B, you'll probably find yourself calling one function from within another.
if (status === ERR_LOGSTOR) {
console.warn('Log storage error: ' + status);
}
When we ran the storeLog
function we also grabbed its return value. Now we're checking it against our known error messages, and if there was a match, we're warning the console operator there was a problem.
}
}
Here we're closing the else
case and closing the function.
logMessage('System started');
Finally we set off the whole chain of events by calling logMessage
to log a message that the system has started.
Move on when you're ready to part 2 of "How to Code: The Fundamentals".