Module 1 · Understanding the layers

1.0 Where code runs, and what it turns into

Lesson 1.0 · 9 min read

Module 0 gave you the shape of a system: two computers talking, static and dynamic, latency, structure. Before we look at the code that fills that shape, one thing has to be settled, because everything in this module and the next depends on it and almost nobody explains it.

Code does not run. Something runs code. Which something, on which machine, is the question that quietly determines nearly everything about how a piece of software behaves, what it can reach, what it can be trusted with, and what it turns into before it executes.

You will shortly read a fetch call inside a React component and, one post later, a fetch call inside a backend controller. They look identical. They exist in different universes. This post is about why.


A runtime is a program that runs your program

Your code is text. A file full of characters that a person typed. Nothing about it does anything.

To make it act, another program has to read it and carry out what it describes. That program is the runtime, and it is doing the actual work: allocating memory, talking to the operating system, opening network connections, drawing pixels. Your code is a set of instructions handed to it.

This is why the same file can behave differently in different places, and why the question "where does this run" is not pedantic. It's asking: which runtime, on which machine, with what powers?

Two broad ways the handoff happens, and you'll hear both words.

Interpreted means the runtime reads your code and executes it as it goes, more or less as written. You change a line, you run it, it does the new thing immediately. Fast to work with, and the mistakes in your code are discovered when the line is reached, which is to say, sometimes in production, in front of someone.

Compiled means a separate program translates all of your code, in advance, into a lower-level form the machine executes directly. The translation step, the compile, takes time and does something valuable: it reads everything and refuses to proceed if parts of it don't make sense together. Errors are found before the program ever runs.

The distinction has blurred, and modern runtimes do sophisticated things in between. What survives, and what you should hold, is the trade underneath: you can find problems before running, or you can find them while running. Everything from type systems to build steps to testing is somewhere on that line, and the direction of the whole industry has been toward moving discovery earlier, because a mistake caught at your desk is free and the same mistake caught by a user is not.


JavaScript lives in two worlds

Now the specific confusion, the one that trips up nearly everyone who learns frontend and backend in the same breath.

JavaScript was created to run inside a browser. The browser contains a JavaScript runtime, and it hands that runtime a very particular set of powers, along with very particular restrictions.

In the browser, code can change the page, respond to clicks, and make network requests. It cannot read the files on your computer, or open a database connection, or listen on a port. Those restrictions are the entire reason you can visit an unfamiliar website without it emptying your hard drive. The browser is a sandbox, and code inside it is powerful in exactly one direction: it can manipulate a page and talk over the network, and nothing else.

And every line of it is visible to the user. It was downloaded to their machine. They can read it, change it, run it differently. This isn't a flaw to work around; it's the nature of shipping code to somebody else's computer, and it's why post 2.4 insists so hard that the frontend is a suggestion rather than a boundary, and why an API key placed there is a published key.

Then someone took that runtime out of the browser and let it run on a server. Same language, entirely different powers. On a server there's no page to manipulate and no user watching. Instead the code can read files, open database connections, listen on a port, hold secrets, and do things that would be unthinkable on a stranger's machine.

So the same word, JavaScript, names code running in two environments whose capabilities barely overlap. The fetch in a React component is a browser asking a server for data, over the network, from a stranger's machine, with everything visible. The fetch in a backend controller is your server, holding your credentials, calling another service, invisibly. Identical text. Different universe, different trust, different consequences.

Hold that and a great deal of frontend confusion dissolves. Why can't I read a file from my React component? Because the browser refuses, and thank goodness. Why is my API key exposed? Because you put it in code that was downloaded to a stranger's machine. Why does this environment variable work on the server and not in the browser? Because they are different programs, running in different places, and only one of them was given that value.


What the browser gives you: the DOM

You're about to learn that React changes state and the interface follows. It's worth knowing what it's changing.

When a browser receives HTML, it doesn't keep the text. It parses it into a live tree of objects in memory: this element contains that element, which has these attributes and this text. That tree is the DOM, the Document Object Model, and it is the actual thing you see. The HTML was a description; the DOM is the reality that gets rendered.

And it is programmable. JavaScript in the browser can reach into that tree and change it: add a node, remove one, change some text, alter a style. The moment it does, the browser redraws. That is the only way anything on a web page ever changes. Every animation, every dropdown, every updated counter, everything: someone modified the DOM.

Which lets you understand what React actually is, and this will make post 1.2 land properly. Manipulating the DOM directly is tedious and error-prone, because you must describe every change: find this element, update its text, add that class, remove that node. You are giving instructions.

React lets you instead describe what the interface should look like for a given state, and then it figures out the DOM changes required to make reality match. State changes, React computes the difference between what's on screen and what should be, and applies exactly those DOM operations.

So when the last post's mental model says "the screen is a reflection of the state," this is the machinery beneath the sentence. There is no magic. There is a tree of objects, and something calculating the minimum set of changes to it. React is a way of not writing DOM instructions by hand, and knowing that it exists to solve that problem is why its design makes sense.


Data formats: what each kind of text is for

One more clarification, because these words get used interchangeably and they name genuinely different categories.

Markup describes structure and meaning within a document. HTML is markup: this is a heading, this is a paragraph, this is a list. It's meant for a document that will be rendered and read, and it carries semantics about what each part is.

Data interchange is for moving structured information between programs. JSON is the dominant one, and you learned to read it in post 2.2: labeled collections and lists. It has no notion of headings or paragraphs, because no human is meant to read it as a document. Its only job is to represent values, faithfully, so another program can use them.

This is why an API returns JSON rather than HTML. The server is not sending a document to be read. It's sending facts to a program that will decide how to present them, which is exactly the separation from post 0.1: the warehouse ships raw goods, and the storefront arranges the display.

Configuration formats describe settings for a program, and are optimized for humans to write and read. YAML is the common one, which you'll meet in deployment and container files, and it exists because writing configuration in JSON is unpleasant, since JSON permits no comments and punishes a missing comma.

And tabular formats like CSV are rows and columns, nothing more. No nesting, no types, no structure beyond a grid. Which is why a spreadsheet exports to CSV cleanly and why a nested object does not.

Four categories: describe a document, move data between programs, configure a program, hold a table. When you know which category something is, you know what it can and cannot express, and you stop being surprised when it can't do something it was never for.


Types: finding mistakes before running

A last thread, and it's the one that grows more useful the more code is written by machines.

In many languages, nothing checks that the value in a variable is the kind of thing your code assumes. You wrote user.name, and if user happens to be nothing at all, you find out when that line runs, as an error, in front of somebody. This is the error you decoded in post 1.4, and it is the most common runtime failure in web development.

A type system lets you declare what kind of thing each value is, and then a program checks, before anything runs, that your code is consistent with those declarations. A function that requires a user cannot be called with a number. A field that might be absent cannot be read without handling the absence. These are caught at your desk, immediately, not by a user on Tuesday.

The cost is that you write down more, and the compiler occasionally argues with you about something you're certain is fine.

The benefit is worth stating in this series' terms specifically. A type declaration is a contract that a machine enforces. It is the same idea as a database constraint from post 3.4 and the same idea as a test from post 4.3: a statement about what must be true, checked automatically, forever. And it constrains what an AI can generate wrongly. If a function's signature says it returns a list of users and the generated implementation returns something else, that is caught in seconds, without anyone reading the code.

In a world where the implementation is cheap and correctness is the scarce thing, every mechanism that pins down intent and lets a machine check it becomes more valuable, not less. Types are one such mechanism. Tests are another. Constraints are a third. Notice that this series keeps arriving at the same idea from different directions, and that it is probably the most important idea in it.


Do this before the next post

Open a browser, open DevTools, and go to the Elements tab, which you've seen before. This time understand what you're looking at: not the HTML file the server sent, but the live DOM, the tree of objects that is currently being rendered. Right-click an element and delete it, and watch it disappear from the page. You just modified the DOM. Reload, and it comes back, because the DOM is rebuilt from the HTML each time and your change was never anywhere but memory.

Then, in the Console tab, type something that changes the page. Any AI will give you a line that finds an element and changes its text. Run it and watch the page change. That single line is what every framework, ultimately, is a sophisticated way of not writing.

Finally, take one thing you've built and answer, for each file, where it runs. Browser or server? Then ask the consequence: can a user read this file's contents? Can this code open a database connection? Should a secret ever appear in it?

If any file makes you hesitate, that hesitation is the most useful thing in this post, because the boundary between those two worlds is where most security mistakes in modern applications are made, and you've just learned to see it.