The Real Definition Of Javascript
Breaking apart the intimidating one-sentence definition of JavaScript, high-level, prototype-based, multi-paradigm, dynamically typed, single-threaded, into three plain questions worth actually understanding.
Post 22 closed out the Asynchronous JavaScript chapter with .map(), Promise.all, and a working slideshow. Before this course goes any deeper into async code, it pulls all the way back to a bigger question: what is JavaScript, actually, underneath the syntax you have already been writing for twenty-two posts?
The textbook answer sounds harmless enough. JavaScript is a high-level, object-oriented, multi-paradigm programming language.
Then someone hands you the honest version, and it reads like a threat: high-level, prototype-based, object-oriented, multi-paradigm, interpreted or just-in-time compiled, dynamic, single-threaded, garbage-collected, with first-class functions and a non-blocking event loop concurrency model.
Nine words, dropped on you at once, none of them explained. My first instinct reading that sentence was to skip it and get back to code.
That instinct is wrong, and here is why. Every one of those nine words is the answer to a question you will eventually ask anyway, usually while debugging something that makes no sense. Knowing the answer in advance is the difference between "why is this broken" and "oh, right, that's just how the engine works."
Three Questions, Not Nine Words
Read the sentence again and the nine words actually cluster into three separate questions:
- How does my code get turned into something the machine can run?
- What shape am I allowed to write that code in?
- What actually happens while it executes?
ExpandNine words from the JavaScript definition sorted into three groups: getting to machine code, organizing your code, and how it actually runs.
Everything below follows that grouping, not the order the words showed up in the original sentence.
Question One: How Does My Code Become Something the Machine Can Run?
A CPU only understands zeros and ones. Every language, no matter how friendly it looks, eventually has to become that.
Low-level languages like C make you do part of that work yourself. You ask the machine directly for a chunk of memory, and you are the one responsible for giving it back when you are done. Forget to give it back, and that memory is gone for the life of the program.
JavaScript skips that entirely. This is what "high-level" actually means: the language builds an abstraction over the machine so you never touch memory addresses by hand.
That abstraction has to be paid for somewhere, and JavaScript pays for it with garbage collection: an algorithm running quietly inside the engine that walks through memory, finds objects nothing references anymore, and frees them. You never call it, and you never even see it run.
It just means an array you stopped using ten functions ago eventually stops taking up space, without you writing a single line to make that happen.
The last piece is turning your actual JavaScript text into machine code. That happens through either compiling (translate everything up front) or interpreting (translate and run line by line), and JavaScript engines like V8 actually do a mix of both, called just-in-time compilation (the next post in this chapter covers that mix in full).
The tradeoff for all three of these, abstraction, garbage collection, and JIT compilation, is the same one: easier to write, slower than a language that hands you full control. That is a fair trade for almost everything you will build. It is a bad trade only for the rare case where every microsecond actually counts.
Question Two: What Shape Am I Allowed to Write This In?
A paradigm is just a mindset for structuring code, and most languages force you into exactly one. JavaScript does not.
You can write it procedurally, a script that runs top to bottom with some functions along the way. You can write it in an object-oriented style, modeling your problem as objects with their own data and behavior. You can write it functionally, passing functions around like data and avoiding shared mutable state.
JavaScript does not pick one for you. You pick, per file, sometimes per function. That flexibility is exactly why the language ends up in so many different kinds of codebases, from a ten-line script tag to a full backend service.
The object-oriented side of that flexibility deserves its own explanation, because it does not work the way OOP works in most other languages.
Prototype-Based, Not Class-Based
In a lot of languages, a class is a rigid template stamped out ahead of time, and an object is just an instance of it. JavaScript's version is looser: objects inherit directly from other objects, through something called a prototype.
Almost everything in JavaScript is actually an object. The exceptions are the primitives, numbers, strings, booleans, and a couple of others. Arrays, which feel like their own basic type, are really just objects with some extra behavior layered on.
You have already used that extra behavior without naming it. Every array you have created in this series has a .length property and a .push() method that you never wrote yourself.
const cities = ['Lisbon', 'Nairobi', 'Auckland'];
console.log(cities.push('Vancouver')); // 4
console.log(Object.getPrototypeOf(cities) === Array.prototype); // trueNeither push nor length lives on cities itself. They live once, on Array.prototype, a single shared object that every array you create quietly links back to. Ask an array for a method it does not have, and JavaScript walks up to the prototype and finds it there instead. That single mechanic, one object deferring to another when it comes up empty, is prototypal inheritance, and it is worth its own dedicated post once this course gets there in full.
First-Class Functions
The other half of "what shape can I write this in" comes down to how JavaScript treats functions themselves: as values, not as a special separate category.
You can store a function in a variable, put one inside an array, or hand one to another function as an argument. Plenty of mainstream languages cannot do this at all, or bolt it on awkwardly.
Say a signup form needs to validate an email before submitting it. Instead of hardcoding one validation rule inside the submit handler, you can pass the rule in as its own function:
function submitForm(value, validator) {
if (!validator(value)) {
console.log('Rejected: failed validation');
return;
}
console.log(`Submitted: ${value}`);
}
const isValidEmail = (value) => value.includes('@') && value.includes('.');
submitForm('neal@example.com', isValidEmail); // Submitted: neal@example.com
submitForm('not-an-email', isValidEmail); // Rejected: failed validationsubmitForm does not know or care what isValidEmail checks for. Swap in a different validator, a phone number check, a password strength rule, and submitForm never changes. That is the entire point of a first-class function: behavior becomes something you can pass around like a string or a number, instead of something baked permanently into one place.
Question Three: What Actually Happens While It Runs?
Two of the remaining words describe how JavaScript behaves the moment it starts executing, and one describes something this series has already spent an entire post on.
Dynamic means dynamically typed: a variable's type is not decided until the engine actually runs that line, and reassigning the variable can change its type entirely.
let value = 'Lisbon'; // string
value = 12; // now a number, no error, no complaintPlenty of developers consider this a design flaw, which is exactly why TypeScript exists, layering static types on top of the same language. That is a deliberate tradeoff, not a bug, and it is a big enough topic to deserve its own post later in this course.
The last piece, single-threaded with a non-blocking event loop, sounds like the scariest one on the list, and it is genuinely the most complex topic in the entire course. A thread, in plain terms, is just a sequence of instructions a computer's processor works through one at a time, and a JavaScript program only ever gets one of them.
The short version: JavaScript can only do one thing at a time on its single thread, so long-running work like a network request gets handed off elsewhere and only comes back to that thread once it is done.
This series already covers that mechanism in real depth, the call stack, the Web APIs environment, the callback queue, and the microtask queue that lets Promise callbacks jump ahead of everything else. If any of that sounds unfamiliar, the event loop post from the async chapter is the one to read before going further, since the rest of this "how JavaScript works" chapter builds directly on top of it rather than re-explaining it from scratch.
The Essentials
- JavaScript trades manual control for ease of use. High-level abstraction, garbage collection, and JIT compilation all exist to keep you from managing memory or machine code by hand, at the cost of raw performance.
- JavaScript does not force one coding style on you. Procedural, object-oriented, and functional code can all live in the same codebase, sometimes the same file.
- Prototypal inheritance means objects share behavior through a linked blueprint, not through rigid classes stamped out ahead of time. That is why every array already has
.push()without you writing it. - First-class functions mean behavior itself becomes a value you can pass around, store, and swap out, the same way you would with a string or a number.
- Dynamic typing resolves a variable's type at runtime, and can change entirely on reassignment, a deliberate tradeoff TypeScript exists to opt out of.
- Single-threaded and non-blocking are not contradictions. One thread runs the code, but long-running work gets handed off elsewhere so that thread is never stuck waiting, already covered in full in this series' event loop post.
Keep reading