Skip to content
← back to profile

JavaScript and TypeScript

The event loop, closures, coercion, and what TypeScript actually checks.

01

The event loop

beginner

JavaScript runs your code on one thread. Everything else, timers, network responses, file reads, user events, happens elsewhere and is handed back as a task to run when the thread is free. The event loop is the mechanism: run the current task to completion, then take the next one.

The rule that follows is that nothing else happens while your code runs. A loop that takes 300 milliseconds blocks rendering, input handling and every pending callback for 300 milliseconds. On a page, that is a frozen interface; in Node, that is every concurrent request waiting. This is the single most important operational fact about the language.

Not all queued work is equal. Promises resolve on the microtask queue, which is drained completely after the current task and before the next one, while setTimeout schedules a macrotask that waits its turn. So a promise chain that never awaits anything real can starve the timer queue, and a setTimeout with zero delay still runs after every pending promise callback.

Async and await do not add threads. They mark the points where a function may pause and let the loop run something else, then resume. That is why an await inside a loop serialises the whole loop, and why Promise.all is the difference between ten sequential requests and ten concurrent ones. The work is still on one thread; only the waiting overlaps.

why this choice

One thread means the cost of any slow synchronous operation is paid by everything else in the program. Knowing what yields and what does not is the difference between a page that stays responsive and one that freezes for reasons nobody can see in the code.

check yourself

setTimeout(fn, 0) is called, and a resolved promise's then is queued. Which runs first?

ask about this
Answers are generated and can be wrong. The topic above is the reviewed version.
02

Closures, prototypes and this

intermediate

A closure is a function plus the variables it captured where it was defined, and it is the mechanism behind most JavaScript patterns: callbacks that remember context, module privacy, function factories, hooks. Understanding that the function holds the variable rather than its value at the time explains both the power and the classic loop bug that let was introduced to fix.

Objects inherit through a prototype chain rather than through classes. A property lookup walks from the object to its prototype and onwards until it finds the name or runs out, and class syntax is a more familiar spelling of exactly that. Knowing the chain exists explains why adding a method to a prototype affects every existing instance, and why a property that shadows one further up hides rather than replaces it.

Then there is this, which is bound by how a function is called rather than where it is defined. Extract a method from an object and call it on its own and this is no longer that object, which is the source of the callback that mysteriously stops working. Arrow functions do not bind their own this, taking it from the enclosing scope, which is why they are the default inside callbacks and the wrong choice for an object method that needs the receiver.

The practical rules: use arrow functions for callbacks, use regular functions or classes for methods that need this, and prefer passing values explicitly over relying on binding. Most this-related bugs disappear when the code stops depending on how a function will be called later.

why this choice

Closures and prototypes are the two mechanisms the whole language is built from, and this is the one piece of it decided at the call site rather than at the definition. Every framework pattern that looks like magic is one of the three.

check yourself

A method is extracted from an object and passed as a callback, and this becomes undefined. Why?

ask about this
Answers are generated and can be wrong. The topic above is the reviewed version.
03

Equality, coercion and the sharp edges

intermediate

JavaScript will convert types to make a comparison work, and the rules are more elaborate than anyone can hold in their head. The practical answer is to use strict equality, which compares without converting, and to convert deliberately when you mean to. Almost every surprising comparison in the language comes from the loose operator being allowed to guess.

The specific facts worth memorising are short. NaN is not equal to itself, which is why isNaN and Number.isNaN exist. typeof null returns object, a bug preserved since 1995 for compatibility. An empty array is falsy in a boolean context but equal to false and to zero under loose comparison. Adding a number to a string concatenates, and subtracting converts, so the same two values produce a string with one operator and a number with another.

Falsy values are a fixed set worth knowing exactly: false, 0, minus 0, empty string, null, undefined and NaN. Everything else is truthy, including empty arrays and empty objects, which is why checking a response by truthiness rather than by a property is a reliable way to accept something empty as success.

Modern syntax removes most of the remaining traps. Optional chaining reads a nested property without throwing when something in the middle is missing, and nullish coalescing supplies a default only for null and undefined rather than for every falsy value, so a legitimate zero or empty string is no longer replaced by a fallback. That last distinction fixes a whole category of quiet bugs in configuration handling.

why this choice

The language guesses when you let it, and the guesses are consistent rather than sensible. Strict equality, explicit conversion and nullish coalescing remove the guessing, which is why every serious style guide requires them.

check yourself

A config value of 0 keeps being replaced by its default. Which operator is responsible?

ask about this
Answers are generated and can be wrong. The topic above is the reviewed version.
04

TypeScript is structural, and it disappears

advanced

TypeScript checks shapes rather than names. If an object has the properties a type requires, it satisfies that type, whether or not anyone declared a relationship. That is structural typing, and it is why you can pass an object literal to a function expecting an interface without implementing anything, and why two identically shaped types from different libraries are interchangeable.

The second fact is that all of it is erased. Types exist during compilation and produce no runtime code at all, so nothing checks a value at the boundary of the program unless you write that check. An API response cast as a User is a User as far as the compiler is concerned and whatever the server actually sent as far as the program is concerned, which is where a large share of production TypeScript errors come from.

The tools for closing that gap are narrowing and validation. Type guards, discriminated unions and the unknown type let you start from I do not know what this is and prove what it is with code the compiler follows. Using unknown rather than any for external data is the single highest-value habit: any switches the checker off silently, unknown forces you to establish what you have before using it.

Beyond that, the type system is expressive enough to encode real rules: unions to make invalid states unrepresentable, generics to keep containers honest, mapped and conditional types to derive one shape from another so they cannot drift apart. It is also expressive enough to write types nobody can read, and the point at which a type needs a comment to explain it is usually the point to simplify it.

why this choice

Structural typing makes TypeScript pleasant to adopt gradually, and erasure means it protects the inside of the program and nothing at its edges. Validating external data and preferring unknown over any is what turns compile-time confidence into runtime safety.

check yourself

An API response is cast to a User type and a field is missing at run time. What went wrong?

ask about this
Answers are generated and can be wrong. The topic above is the reviewed version.