KB KEDBYTE TECHNOLOGIES PRIVATE LIMITED
CHAPTER
35

Web and App Development

Part G · Building and Shipping Software|30,848 words|about 134 min read|Volume 4

35.0 What this chapter gives you#

  1. You will be able to draw the line between frontend and backend, say exactly what runs on the user’s device and what runs on a server you control, and state the security rule that follows from it.
  2. You will be able to say what HTML, CSS and JavaScript each are, why they are three separate things, and why only one of them is a programming language.
  3. You will be able to explain the CSS cascade, specificity with the real calculation, the box model, flexbox and grid, and stop guessing at layout.
  4. You will be able to describe the JavaScript event loop with the call stack, the task queue and the microtask queue, and predict the output order of a piece of asynchronous code by hand.
  5. You will be able to trace an HTML file all the way to lit pixels: parse, DOM, CSSOM, render tree, layout, paint, composite, and say what reflow costs.
  6. You will be able to say what a frontend framework actually solves, how a virtual DOM and reconciliation work, and when you do not need one at all.
  7. You will be able to write a minimal web server in two languages, explain routing and middleware, and compare Node, Python, Java, Go, Ruby, PHP and .NET on honest grounds rather than fashion.
  8. You will be able to design a small relational schema, write SQL with joins and grouping, explain how a B-tree index turns a scan into a lookup, read an EXPLAIN plan, and define ACID one letter at a time.
  9. You will be able to store a password correctly, choose between sessions and tokens, describe the OAuth 2.0 authorization code flow step by step, and separate authentication from authorization.
  10. You will be able to replace the “180-day roadmap” with an order of learning that matches how long these things really take.

35.1 Frontend, backend and the line between them#

PLAIN35.1.1 in simple words#

  1. A web application lives in two places at once.
  2. Part of it runs on the user’s own device, inside the browser. That part is called the frontend.
  3. Part of it runs on a computer you own or rent, far away. That part is called the backend.
  4. The frontend is what the user sees and touches: text, pictures, buttons, boxes to type in.
  5. The backend is what the user never sees: the stored data, the rules about who may do what, the money, the emails, the search.
  6. The two talk to each other over the network, using the request and reply pattern from the networking chapters.
  7. The device asks, the server answers. The device asks again, the server answers again. That is the whole shape of it.
  8. This is called client-server. The client is the asking side. The server is the answering side.
  9. Here is the single most important rule in this whole chapter. Everything on the user’s device can be changed by the user.
  10. The user can edit the page, change the numbers you sent, delete your checks, and send you anything at all.
  11. So the frontend can be polite, helpful and fast. It can never be trusted.
  12. Every rule that matters must be checked again on the server, where the user cannot reach it.

PLAIN35.1.2 a picture in your head#

  1. Think of a bank branch.
  2. The counter, the forms, the pen on a chain and the queue barrier are the frontend. They are in the public part of the building.
  3. The vault, the ledgers and the staff-only room are the backend.
  4. The form asks you to write your account number in the right shape. That is helpful. It stops honest people making honest mistakes.
  5. But nobody thinks the form is security. A person can write anything on a form. They can bring their own form. They can shout their request instead.
  6. So the clerk checks the request again, against the real ledger, behind the glass, before any money moves.
  7. That second check is the backend check. The form was the frontend check.
  8. Both are useful. Only one of them is protection.

Where this comparison breaks:

  1. In a bank you can see the customer. On the web you cannot. The “customer” may be a program sending ten thousand requests a second from another country.
  2. In a bank the forms live in the branch. On the web, you hand the customer a complete copy of your entire form-processing machine and let them run it at home. They can take it apart first.
  3. And a bank branch serves one person at a time at each counter. A server handles thousands of half-finished conversations at once.

PLAIN35.1.3 a worked example#

  1. A shop page shows a jacket at 4,000 rupees. The page has a discount box.
  2. The frontend code checks the discount code, works out 10 percent off, and shows 3,600.
  3. The user then presses Buy. The browser sends a message to the server.
  4. A careless developer sends the price in that message:
{ "item": "jacket-01", "price": 3600, "code": "SAVE10" }
  1. The user opens the browser’s developer tools, changes one number, and sends this instead:
{ "item": "jacket-01", "price": 1, "code": "SAVE10" }
  1. If the server saves that order, the shop has just sold a jacket for one rupee. Nothing was hacked. The user simply typed a different number.
  2. The correct message contains no price at all:
{ "item": "jacket-01", "code": "SAVE10", "qty": 1 }
  1. The server looks up the real price of jacket-01 in its own database, checks whether SAVE10 is real, unexpired and allowed for this user, and computes the total itself.
  2. The frontend showed 3,600 so the user was not surprised. The server decided 3,600 because it is the only side that can decide.
  3. This exact mistake has cost real companies real money many times.

PLAIN35.1.4 what is really happening inside#

  1. The browser is a program on the user’s machine. Chapter 18 called such a program a process: code plus memory, run by the operating system.
  2. When you visit a site, the browser makes a network connection to a server, as in Chapters 28 and 32, and asks for a document.
  3. The server sends back text. Usually HTML, which describes the content.
  4. The browser reads that text and builds an internal model of the page in its own memory. Then it draws that model on the screen.
  5. The HTML normally refers to more files: stylesheets, scripts, images, fonts. The browser fetches each of those with more requests.
  6. Once a script is running, it can make further requests on its own, without loading a new page. That is how modern pages update in place.
  7. Every one of those requests is a separate arrival at the server, and the server has no memory of the previous one unless you build that memory.
  8. That last point is what “HTTP is stateless” means, and it is why sessions and tokens exist at all, which is section 35.12.
  9. The split exists for three reasons. Speed, because a round trip to a server costs tens or hundreds of milliseconds and a local reaction costs none.
  10. Control, because your data and your rules must live where users cannot edit them.
  11. Scale, because one server can serve many clients only if the clients do their own drawing.

TECHNICAL35.1.5 the engineer’s version#

  1. The client-server model is an architectural style, not a protocol. HTTP is one instance of it. It was covered from the wire upward in Chapters 23 to 33.
  2. HTTP/1.1 is defined by RFC 9110 to RFC 9112 (June 2022), which replaced the older RFC 7230 series. HTTP/2 is RFC 9113. HTTP/3 over QUIC is RFC 9114.
  3. HTTP is stateless by design: each request carries everything needed to process it. State is reconstructed from cookies, headers or tokens.
  4. The trust boundary sits at the network edge of your server. Everything on the far side of it is untrusted input, including your own JavaScript.
  5. The formal rule is the end-to-end argument from Saltzer, Reed and Clark (1981, 1984): a check is only meaningful at the point that owns the outcome.
Concern Frontend can Server must
Field format show hints early validate again
Price, totals display only compute, authoritative
Permissions hide buttons enforce on every call
Rate limits debounce clicks count and reject
Secrets never hold any hold all of them
  1. Client-side validation is a usability feature with a measurable benefit: it removes a network round trip, typically 30 to 300 ms depending on distance.
  2. Anything shipped to the browser is public. API keys in a bundle, “hidden” admin routes and commented-out code are all readable. Minification is not obfuscation and obfuscation is not security.
  3. Tools that make this concrete: browser DevTools Network and Sources panels, curl -v, Burp Suite and mitmproxy for intercepting and editing requests.
  4. Server-side rendering and client-side rendering shift where HTML is produced, but they do not move the trust boundary at all. That is fixed.

WORDS35.1.6 remember these#

  1. Frontend — the part running on the user’s device — client-side code executing in the browser’s JavaScript engine and rendering engine.
  2. Backend — the part running on your machine — server-side processes you control, holding data, secrets and authorization logic.
  3. Client-server — one side asks, the other answers — an architectural style where a requesting process contacts a listening service process.
  4. Stateless — the server forgets between requests — no server-held context is implied by the protocol; state must be carried or looked up.
  5. Trust boundary — the line where you stop believing input — the point where data crosses from an environment you do not control into one you do.

35.2 HTML: the structure of a page#

PLAIN35.2.1 in simple words#

  1. HTML stands for HyperText Markup Language.
  2. A markup language is a way of adding labels to text to say what each part of the text is.
  3. It does not say what the text should look like. It says what the text means.
  4. You mark up a piece of text by wrapping it in a tag. A tag is a word inside angle brackets.
  5. <p>Hello</p> says: the word Hello is a paragraph.
  6. Most tags come in pairs. An opening tag, then the content, then a closing tag with a slash.
  7. The opening tag, the content and the closing tag together are called an element.
  8. Tags can carry extra information called attributes, written as name="value" inside the opening tag.
  9. Elements go inside other elements. That is nesting, and it makes the page a tree, like folders inside folders.
  10. HTML is not a programming language. It cannot add two numbers, make a decision, or repeat something. It only describes structure.
  11. That is not an insult. Describing structure well is the whole job, and most accessibility and search problems come from doing it badly.

PLAIN35.2.2 a picture in your head#

  1. Think of a printed book being prepared before typesetting.
  2. An editor goes through the manuscript with a pencil and writes in the margin: this line is a chapter title, this block is a quotation, this is a footnote, this is a list.
  3. The editor does not choose the font. The editor does not choose the size. Those come later, from the designer.
  4. The editor’s marks are the markup. They record what each piece of text is.
  5. A different designer can take the same marked-up manuscript and produce a paperback, a large-print edition, or an audiobook script.
  6. Because the marks say “chapter title”, the audiobook narrator knows to pause and change tone. The word “chapter title” carries meaning that “big bold text” does not.

Where this comparison breaks:

  1. The editor’s marks are read by a human who can use judgement. HTML is read by a machine that will do exactly and only what the tags say.
  2. A book is finished once. An HTML page is re-laid-out every time the window size changes, on screens the author never saw.
  3. And HTML is more forgiving than any editor would be. Browsers repair broken markup silently, which hides mistakes rather than reporting them.

PLAIN35.2.3 a worked example#

  1. Here is a complete, valid page. Everything in it earns its place.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport"
        content="width=device-width, initial-scale=1">
  <title>Bookshop - Contact</title>
  <link rel="stylesheet" href="/style.css">
</head>
<body>
  <header>
    <h1>Ambleside Bookshop</h1>
    <nav aria-label="Main">
      <a href="/">Home</a>
      <a href="/contact" aria-current="page">Contact</a>
    </nav>
  </header>

  <main>
    <h2>Send us a message</h2>
    <form action="/contact" method="post">
      <label for="name">Your name</label>
      <input id="name" name="name" type="text" required>

      <label for="email">Email</label>
      <input id="email" name="email" type="email" required>

      <label for="msg">Message</label>
      <textarea id="msg" name="msg" rows="5"></textarea>

      <button type="submit">Send</button>
    </form>
  </main>

  <footer>
    <p>Open 9am to 6pm, closed Sunday.</p>
  </footer>
</body>
</html>
  1. Line by line, the parts that people get wrong.
  2. <!DOCTYPE html> is not a tag. It is a switch. Without it, browsers fall into “quirks mode” and copy 1990s bugs on purpose.
  3. lang="en" tells screen readers which language to pronounce, and tells search engines who this page is for.
  4. <meta charset="utf-8"> says the bytes are UTF-8, from Chapter 7. Leave it out and names with accents may turn into rubbish.
  5. The viewport meta tag tells a phone not to pretend it is a 980-pixel desktop. Without it, your responsive CSS does nothing on mobile.
  6. <label for="name"> is joined to <input id="name"> by the matching value. Now clicking the label focuses the box, and a screen reader announces the right words.
  7. name="name" is what the server receives. id is for the page. They are different jobs that beginners merge.
  8. type="email" makes phone keyboards show the at sign, and lets the browser check the shape for free.

PLAIN35.2.4 what is really happening inside#

  1. The browser receives the file as a stream of bytes, not as a page.
  2. It decodes those bytes into characters using the declared encoding, then splits the characters into tokens: start tag, end tag, text, comment.
  3. It builds a tree from those tokens. Each element becomes a node with a parent and a list of children. That tree is the DOM, which is section 35.5.
  4. Semantic elements are tags whose names describe the role of the content: header, nav, main, article, section, aside, footer, h1 to h6, figure, time.
  5. They matter for two audiences you cannot see.
  6. Screen readers build a list of headings and landmarks from them, so a blind user can jump straight to the main content instead of hearing the menu on every page.
  7. Search engines use them to work out what the page is about and which part is the real content.
  8. A <div> says nothing. It is a plain box. A page built only from divs looks identical and is much harder to use without sight.
  9. Forms are the one place plain HTML can send data. A form with method="post" packages the named fields and sends them to the server on its own, with no JavaScript at all.
  10. The browser gives you validation, keyboard handling, autofill and mobile keyboards for free, but only if you use real form elements.

TECHNICAL35.2.5 the engineer’s version#

  1. HTML was created by Tim Berners-Lee at CERN; the first public description circulated in 1991. HTML 2.0 was standardized as RFC 1866 in November 1995.
  2. HTML 4.01 became a W3C Recommendation in December 1999. XHTML 1.0 followed in 2000 and pushed strict XML syntax, which the web rejected.
  3. WHATWG formed in 2004 in response. HTML5 became a W3C Recommendation on 28 October 2014. Since a 2019 agreement, the WHATWG HTML Living Standard is the single normative specification. There are no numbered versions now.
  4. The parsing algorithm is fully specified, including error recovery. Two conforming browsers must build the same DOM from the same broken input. This is a standard, not a convention, and it is unusual in being so.
  5. Elements are void (img, br, input, meta), raw text (script, style), or normal. Void elements have no closing tag and no children.
  6. Attribute values should be quoted. Unquoted values are legal in HTML5 but break on spaces. Quoting is a convention with a good reason.
  7. ARIA (Accessible Rich Internet Applications, WAI-ARIA 1.2, W3C Recommendation June 2023) adds roles and states for widgets HTML lacks.
  8. The first rule of ARIA, stated in the specification’s own authoring practices, is not to use ARIA when a native element exists. A real <button> beats <div role="button" tabindex="0"> on every measure.
Input type Added in Mobile keyboard Free validation
text HTML 2.0 standard none
email HTML5 at sign shape check
number HTML5 digits min, max, step
date HTML5 date picker range
tel HTML5 phone pad none
  1. Tools: the W3C Nu HTML Checker (validator.w3.org/nu), axe DevTools, and the accessibility tree view in Chrome and Firefox DevTools.

WORDS35.2.6 remember these#

  1. Markup — labels added to text — a syntax that annotates content with structural and semantic roles rather than presentation.
  2. Element — a tag plus its content — a node in the document tree with a tag name, attribute set and child node list.
  3. Attribute — extra detail on a tag — a name-value pair on an element, exposed in the DOM as content attributes and IDL properties.
  4. Semantic HTML — tags that say what content is — markup chosen so the accessibility tree and machine readers infer correct roles.
  5. Void element — a tag with no closing half — an element defined as having no end tag and no permitted children, such as img or input.
  6. Doctype — the switch that turns on modern rules — a legacy string that selects no-quirks mode in the HTML parser.

35.3 CSS: presentation, and the cascade that confuses everyone#

PLAIN35.3.1 in simple words#

  1. CSS stands for Cascading Style Sheets.
  2. HTML says what things are. CSS says what they look like.
  3. Keeping those two apart is the idea called separation of content and presentation.
  4. The benefit is real. Change one stylesheet and every page changes. Give the same HTML a different stylesheet and you get a different design.
  5. A CSS rule has two halves. A selector picking which elements to affect, and a block of declarations saying what to do to them.
  6. A declaration is a property and a value: color: black;.
  7. Many rules can hit the same element. When two rules disagree, the browser has to pick one. The picking procedure is called the cascade.
  8. The cascade is where almost everyone gets stuck, so we will do it properly and give you the real calculation.
  9. Every element is drawn as a rectangle. Understanding that rectangle, the box model, removes most layout confusion.
  10. Two layout systems, flexbox and grid, do nearly all modern layout. Learn those two well and you can stop guessing.

PLAIN35.3.2 a picture in your head#

  1. Imagine a school with a stack of rulebooks about uniform.
  2. The national rule says shoes must be black. The school rule says shoes must be brown. The class teacher’s note says shoes must be blue.
  3. A pupil arrives. Which colour wins?
  4. You need an agreed order for settling arguments, or the pupil cannot dress.
  5. The school’s order is: a note about this named pupil beats a note about this class, which beats a note about all pupils.
  6. If two notes are equally specific, the one written most recently wins.
  7. And a rule marked “no exceptions, headmaster” beats everything, which is why the headmaster should use it almost never.
  8. That is the cascade. Origin, then importance, then specificity, then order.

Where this comparison breaks:

  1. Rules about pupils apply to whole pupils. CSS settles each property separately. Colour can come from one rule and size from another, in the same element, at the same time.
  2. And some CSS properties are inherited by children automatically, like text colour, while others are not, like border. No school rule works that way.

PLAIN35.3.3 a worked example#

  1. Here is a real box, measured.
.card {
  width: 300px;
  padding: 20px;
  border: 2px solid black;
  margin: 16px;
  box-sizing: content-box;
}
  1. With content-box, which is the original CSS behaviour, width means the content area only.
  2. Total horizontal space the box occupies on screen:
Part Left Right Total
content - - 300
padding 20 20 40
border 2 2 4
margin 16 16 32
  1. Drawn width is 300 + 40 + 4 = 344 px. Space consumed in the flow, including margin, is 376 px.
  2. That surprise is why almost every project now writes:
*, *::before, *::after { box-sizing: border-box; }
  1. With border-box, width: 300px means the border edge is 300 px, so the content area shrinks to 300 - 40 - 4 = 256 px. What you type is what you measure.
  2. Now specificity, calculated properly. Specificity is a triple: (A, B, C).
Selector A ids B classes C elements
p 0 0 1
.note 0 1 0
p.note 0 1 1
#main p.note 1 1 1
:is(#a, p) 1 0 0
  1. Compare left to right. (1,0,0) beats (0,9,9). A single id beats nine classes. The columns are not digits of one number, they are separate ranks.
  2. Attribute selectors and pseudo-classes count in column B. Pseudo-elements such as ::before count in column C. The universal selector * counts zero.
  3. :where(...) always contributes zero, which makes it the correct tool for library defaults you want authors to override easily.

PLAIN35.3.4 what is really happening inside#

  1. The browser collects every declaration that could apply to one element and one property, then sorts them by these steps in order.
  2. Step one, origin and importance. Order from weakest to strongest: browser defaults, then user settings, then your stylesheet, then your !important rules, then user !important, then browser !important.
  3. Note the flip. Among !important rules the order reverses, so a user’s accessibility override can beat an author’s. That is deliberate.
  4. Step two, since 2023, cascade layers declared with @layer. Later layers beat earlier ones, and this is decided before specificity.
  5. Step three, specificity, using the (A, B, C) triple above.
  6. Step four, source order. If everything else ties, the declaration that appears later in the stylesheet wins.
  7. Inline style="..." is not a selector. It sits above all normal author rules and is only beaten by !important.
  8. If no rule sets a property, the element may still get a value by inheritance from its parent. Text properties inherit. Box properties do not.
  9. If nothing inherits either, the property takes its initial value from the specification.
  10. The honest version: people say “the most specific rule wins”. That is only step three of six, and it is not even the first tiebreaker. The full order is origin, importance, layer, specificity, then source order.

TECHNICAL35.3.5 the engineer’s version#

  1. CSS was proposed by Hakon Wium Lie on 10 October 1994 at CERN, with Bert Bos. CSS Level 1 became a W3C Recommendation on 17 December 1996. CSS 2.1 became a Recommendation in June 2011.
  2. There is no “CSS3” specification. Since Level 2 the language is split into independently versioned modules: Selectors Level 4, Grid Level 2, and so on. Saying “CSS3” is a marketing habit, not a standard.
  3. Display types: block, inline, inline-block, flex, grid, none, and the two-value syntax display: inline flex. none removes the element from layout entirely; visibility: hidden keeps its space.
  4. Flexbox is one-dimensional: items are distributed along a main axis. It reached W3C Candidate Recommendation status in 2012 and shipped broadly from 2013.
.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.toolbar .spacer { flex: 1 1 auto; }
  1. Grid is two-dimensional: rows and columns declared together. It shipped in Chrome 57 and Firefox 52 in March 2017, which is the date modern CSS layout really begins.
.page {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  grid-template-rows: auto 1fr auto;
  gap: 16px;
  min-height: 100vh;
}
  1. The fr unit means one share of the leftover space. minmax(0, 1fr) is the standard fix for grid children that refuse to shrink below their content.
  2. Positioning: static (default), relative (offset from its own place, keeps its space), absolute (removed from flow, positioned against the nearest positioned ancestor), fixed (against the viewport), sticky (relative until a scroll threshold, then fixed).
Unit Relative to Typical use
px device-independent pixel borders, hairlines
em own font-size spacing inside a component
rem root font-size (16 px) type scale, layout
% parent’s same dimension fluid widths
vh / vw 1% of viewport full-screen sections
ch width of “0” glyph text measure limits
  1. One CSS px is defined as 1/96 inch at a nominal viewing distance, not one hardware pixel. On a 3x phone screen, one CSS px covers nine device pixels.
  2. Media queries adapt to the environment:
@media (min-width: 48rem) { .page { --cols: 2; } }
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; }
}
  1. Mobile-first means writing the small-screen rules first and using min-width queries to add complexity. It is a convention, and a good one.
  2. Custom properties, usually called CSS variables, are real cascading values, live at runtime, and are readable by JavaScript. They shipped in 2016.
:root { --gap: 12px; --ink: #111; }
.card { padding: var(--gap); color: var(--ink); }
  1. Container queries let a component respond to its own container rather than the viewport. They reached cross-browser support in February 2023.
.sidebar { container-type: inline-size; }
@container (min-width: 30rem) {
  .card { display: grid; grid-template-columns: 1fr 2fr; }
}
  1. Native CSS nesting reached cross-browser support during 2023 and 2024 and removes one of the main historical reasons to use Sass.
  2. :has(), the long-requested parent selector, shipped across browsers by December 2023. .card:has(img) styles a card because of what it contains.
  3. Tools: DevTools Styles pane shows the winning declaration and strikes out losers; the Layout pane overlays grid and flex lines; @supports queries let you ship progressive enhancement safely.

WORDS35.3.6 remember these#

  1. Cascade — the argument-settling procedure — the ordered algorithm of origin, importance, layer, specificity and source order.
  2. Specificity — how targeted a selector is — the (id, class, type) triple compared left to right, never summed.
  3. Box model — every element is a rectangle — content, padding, border and margin boxes, with box-sizing choosing what width measures.
  4. Flexbox — line things up along one direction — CSS Flexible Box Layout, one-dimensional distribution along a main and cross axis.
  5. Grid — a real table-free grid — CSS Grid Layout, two-dimensional placement with explicit and implicit tracks.
  6. rem — a size tied to the page’s base text — the computed font-size of the root element, 16 px unless changed.
  7. Container query — react to my own box, not the window — @container rules evaluated against a queried ancestor’s size.

35.4 JavaScript: the language and its event loop#

PLAIN35.4.1 in simple words#

  1. JavaScript is the programming language that runs inside web browsers.
  2. Unlike HTML and CSS, it really is a programming language. It can decide, repeat, calculate, store and react.
  3. It was written in 1995 at a company called Netscape, by one person, Brendan Eich, in about ten days.
  4. Ten days is not enough time to design a language carefully. Many of its odd corners come straight from that deadline.
  5. It was never removed, because the web could not break old pages. So the world’s most used language is one that was rushed and then frozen.
  6. Since 1997 the language has had a written standard, and it now gets a new edition every June, so it improves without breaking what came before.
  7. The single hardest idea in JavaScript is asynchrony: starting something slow, not waiting for it, and dealing with the answer when it arrives.
  8. Asynchrony exists because JavaScript in a browser has only one worker. If that worker waits, the whole page freezes: no scrolling, no clicking.
  9. So instead of waiting, it writes a note saying what to do when the answer comes, and gets on with other work.
  10. The machine that manages those notes is called the event loop, and it is the thing you must understand to write correct JavaScript.

PLAIN35.4.2 a picture in your head#

  1. Picture a single cook in a small kitchen with a strict rule: finish the task in your hands before starting the next one.
  2. An order arrives: boil rice, twenty minutes. The cook does not stand and watch the pot. That would block every other order.
  3. The cook puts the rice on, sets a timer, writes “when the timer rings, drain the rice” on a slip, and puts the slip in a tray.
  4. Then the cook takes the next order and starts chopping.
  5. When the cook finishes a task and has nothing in hand, they look in the tray, take the oldest slip, and do it.
  6. That is the event loop. One cook, no waiting, a tray of pending slips.
  7. There is a second, smaller tray, right next to the cook, checked before the big one and emptied completely each time.
  8. Anything urgent, like “the sauce reduced, add salt now”, goes in the small tray and is done before the next big order.
  9. The small tray is the microtask queue. The big tray is the task queue.

Where this comparison breaks:

  1. A real cook can glance at the pot while chopping. JavaScript truly cannot. If a function loops for three seconds, nothing else happens for three seconds, including the timer that was due one second ago.
  2. And a timer set for 100 ms does not mean the slip is done at 100 ms. It means the slip is put in the tray at 100 ms. If the cook is busy, it waits.

PLAIN35.4.3 a worked example#

  1. Predict the output of this. Most people get it wrong the first time.
console.log("1");

setTimeout(() => console.log("2"), 0);

Promise.resolve().then(() => console.log("3"));

queueMicrotask(() => console.log("4"));

console.log("5");
  1. The answer is 1, 5, 3, 4, 2.
  2. Here is why, step by step.
  3. The whole script is itself one task. It runs to the end before anything else is considered. So “1” prints, then “5” prints.
  4. Along the way, setTimeout handed a callback to the timer system with a delay of zero. When zero milliseconds pass, that callback is placed in the task queue.
  5. Promise.resolve().then(...) placed its callback in the microtask queue immediately, because the promise was already settled.
  6. queueMicrotask placed its callback in the microtask queue too, after the promise one.
  7. The script ends. The call stack is now empty.
  8. Before touching the task queue, the engine drains the microtask queue completely. So “3” prints, then “4”.
  9. Only now does it take one item from the task queue. “2” prints.
Moment Stack Microtasks Tasks
script running script 3, 4 (timer pending)
script ends empty 3, 4 2
drain micro 3 then 4 empty 2
next task 2 empty empty
  1. The rule to memorize: microtasks run to exhaustion after every task, before the next task, and before the browser gets a chance to paint.
  2. That gives you a real bug. A microtask that queues another microtask forever starves the page. The browser never repaints. Nothing on screen moves.

PLAIN35.4.4 what is really happening inside#

  1. The call stack is a pile of function calls. Calling a function pushes a frame on. Returning pops it off. When the pile is empty, the engine is idle.
  2. Slow things are not done by JavaScript at all. Network requests, timers, disk access and user input are handled by the browser’s own code, in other threads, written in C++ or Rust.
  3. When one of those finishes, the browser does not interrupt your code. It places your callback in a queue.
  4. The event loop is a simple repeating check: is the stack empty? If yes, drain all microtasks, then take one task, run it, then possibly render.
  5. Promises, async/await, queueMicrotask and MutationObserver use the microtask queue. Timers, network events, clicks and messages use task queues.
  6. await is not magic. It splits your function in two at that point. The rest of the function becomes a microtask attached to the awaited promise.
  7. Rendering happens between tasks, not during one. That is why a long loop freezes the screen even if you change styles inside it.
  8. Three ways to write the same wait, in historical order.
// 1. Callbacks (1995 onward)
getUser(id, (err, user) => {
  if (err) return done(err);
  getOrders(user, (err, orders) => {
    if (err) return done(err);
    done(null, orders);
  });
});

// 2. Promises (standardized in ES2015)
getUser(id)
  .then(user => getOrders(user))
  .then(orders => done(null, orders))
  .catch(done);

// 3. async / await (ES2017)
try {
  const user = await getUser(id);
  const orders = await getOrders(user);
  return orders;
} catch (err) {
  handle(err);
}
  1. All three do the same thing. The third is easiest to read and easiest to get right, because errors travel through ordinary try and catch.
  2. The trap in version three: two sequential await calls that do not depend on each other waste time. Use Promise.all to run them together.
const [user, rates] = await Promise.all([
  getUser(id),
  getRates(),
]);

TECHNICAL35.4.5 the engineer’s version#

  1. Brendan Eich wrote the first prototype in May 1995 at Netscape Communications. It was called Mocha, renamed LiveScript in September 1995, then JavaScript in December 1995 as part of a marketing deal with Sun.
  2. The name is the source of endless confusion. JavaScript and Java are unrelated languages.
  3. Netscape submitted it to Ecma International; ECMA-262 1st edition was published in June 1997. The standard language is called ECMAScript; JavaScript is a trademarked implementation name.
  4. ES5 (December 2009) added strict mode and JSON. ES2015, also called ES6, was the large modernization: let, const, arrow functions, classes, modules, promises, template literals, destructuring.
  5. Since 2015 there is one edition each June. ECMAScript 2025 was the 16th edition (June 2025) and ECMAScript 2026 the 17th (June 2026).
  6. Types: number (IEEE 754 double, so integers are exact only to 2^53 - 1), bigint, string (UTF-16 code units), boolean, undefined, symbol, null, and object.
  7. Coercion is the automatic conversion between types, and it is the source of the famous oddities.
Expression Result Reason
0.1 + 0.2 0.30000000000000004 binary floats
[] + {} “[object Object]” both to string
"5" - 2 3 minus forces number
"5" + 2 “52” plus prefers string
NaN === NaN false IEEE 754 rule
typeof null “object” 1995 bug, frozen
  1. 0.1 + 0.2 is not a JavaScript flaw. It is IEEE 754 binary floating point, from Chapter 7, and it happens in Python, Java and C too.
  2. Use === not ==. == applies the coercion table and produces surprises such as "" == 0 being true.
  3. Closures: a function keeps a live reference to the variables of the scope where it was defined, even after that scope returns.
function counter() {
  let n = 0;
  return () => ++n;
}
const next = counter();
next(); // 1
next(); // 2
  1. JavaScript uses prototypal inheritance. Every object has a hidden link to another object; property lookup walks that chain. class syntax, added in ES2015, is sugar over prototypes, not a separate system.
  2. Modules: ES modules (import, export) are the standard, defined in ES2015 and shipped in browsers from 2017. CommonJS (require, module.exports) is Node’s older system and is still everywhere.
  3. Engines: V8 (Chrome, Node, Edge, Deno), SpiderMonkey (Firefox), JavaScriptCore (Safari, Bun). All are JIT compilers with multiple tiers, described in Chapter 16.
  4. Error handling rules that matter: never swallow an error silently; attach a .catch or try/catch to every promise chain; an unhandled rejection terminates a Node process by default since Node 15 (2020).
  5. Tools: node --prof, Chrome DevTools Performance panel for long tasks, performance.now() for sub-millisecond timing.

WORDS35.4.6 remember these#

  1. Event loop — the one-worker queue system — the algorithm that drains microtasks and dispatches one task per turn on a single thread.
  2. Call stack — the pile of calls in progress — the LIFO structure of execution contexts; overflow raises RangeError.
  3. Microtask — an urgent note done before anything else — a job queued to the microtask queue, drained fully after each task.
  4. Promise — a receipt for a future value — an object in pending, fulfilled or rejected state, with then scheduling microtasks.
  5. Closure — a function that remembers where it was born — a function object plus a reference to its defining lexical environment.
  6. Prototype — the object I fall back to — the internal [[Prototype]] link traversed during property lookup.
  7. ECMAScript — the written standard — ECMA-262, published annually each June since 2015; JavaScript is an implementation of it.

35.5 The DOM and how a browser turns HTML into pixels#

PLAIN35.5.1 in simple words#

  1. HTML arrives as text. Text cannot be clicked, moved or measured.
  2. So the browser converts the text into a live tree of objects held in memory. That tree is the DOM, the Document Object Model.
  3. Every tag becomes a node. Every node knows its parent and its children.
  4. JavaScript does not edit your HTML file. It edits this tree. Change the tree and the screen follows.
  5. Your CSS is turned into a second structure with the same idea, called the CSSOM.
  6. The browser combines the two into a render tree: only the things that will actually be drawn, each with its final style.
  7. Then it works out where every box goes, which is layout. Then it fills in the colours, which is paint. Then it stacks the layers, which is composite.
  8. If you change something that affects size or position, the browser must redo layout. That is called reflow and it is the expensive one.
  9. If you change only a colour, it can skip layout and just repaint. Cheaper.
  10. If you change only a transform or opacity, it can often skip both and just re-stack existing layers. Cheapest of all.
  11. Knowing which of the three you triggered is most of frontend performance.

PLAIN35.5.2 a picture in your head#

  1. Think of a newspaper being made in a print room, before computers.
  2. The reporters’ typed pages are the HTML. Just text with notes on it.
  3. The editor’s style guide, saying headlines are 36 point and captions are italic, is the CSS.
  4. The layout artist takes both and builds a paste-up board: which story goes in which column, how many lines it runs to, where the picture sits. That is layout.
  5. Then the ink goes on: black type, grey photographs. That is paint.
  6. Then the transparent overlay sheets are stacked in order. That is composite.
  7. Now the editor says one caption should be bold. The word gets wider. The line rewraps. The column gets longer. The story below moves down. The whole board must be redone.
  8. That is a reflow, and you can see why it costs.
  9. But if the editor only says “print this headline in a darker grey”, the shapes do not move. Only the ink changes. That is a repaint.

Where this comparison breaks:

  1. A newspaper is laid out once. A browser may redo layout sixty times a second while you drag a window.
  2. And in a print room the artist works on the whole board. Browsers do partial invalidation: they mark a subtree as dirty and try to redo only that. The cost still spreads upward more often than you would like.

PLAIN35.5.3 a worked example#

  1. Here is the classic layout-thrashing bug, in ten lines.
const items = document.querySelectorAll(".row");

// BAD: read, write, read, write, ...
for (const el of items) {
  el.style.height = el.offsetHeight + 10 + "px";
}
  1. offsetHeight is a read of a geometric value. style.height is a write that invalidates geometry.
  2. The browser batches writes and flushes them lazily. But a read forces it to flush immediately, so the answer is correct. That forced flush is a reflow.
  3. With 200 rows, that loop causes 200 forced synchronous layouts. On a mid phone that can be 200 ms of frozen page.
  4. The fix is to separate all reads from all writes.
const items = document.querySelectorAll(".row");

// GOOD: read everything, then write everything
const heights = [...items].map(el => el.offsetHeight);
items.forEach((el, i) => {
  el.style.height = heights[i] + 10 + "px";
});
  1. Now there is one layout pass, not 200. Same output, roughly one hundredth of the cost.
  2. Properties that force a synchronous layout when read include offsetTop, offsetHeight, scrollTop, clientWidth, getBoundingClientRect() and getComputedStyle().
  3. Now events. Suppose a list has 500 rows and each needs a click handler.
// 500 listeners, 500 objects, slow to attach
rows.forEach(r => r.addEventListener("click", onClick));

// 1 listener, using bubbling. This is delegation.
list.addEventListener("click", (e) => {
  const row = e.target.closest(".row");
  if (row) onClick(row.dataset.id);
});
  1. This works because a click on a row also fires on its parents, in order, outward. That travelling is bubbling, and using it deliberately is event delegation.
  2. Delegation also handles rows added later, which the first version cannot.

PLAIN35.5.4 what is really happening inside#

  1. The full sequence, in order, for a first page load.
bytes -> characters -> tokens -> nodes -> DOM tree
CSS bytes ------------------------------> CSSOM tree
DOM + CSSOM -> render tree -> layout -> paint -> composite
  1. Parsing is incremental. The browser starts building the DOM before the whole file has arrived, which is why a slow page still shows its header early.
  2. A plain <script> tag in the middle of the body stops parsing dead. The browser must fetch and run it before continuing, because the script might write into the document.
  3. defer says: fetch it now in parallel, run it after parsing finishes, in order. async says: fetch now, run the moment it arrives, order not guaranteed. Use defer for application code.
  4. CSS is render-blocking. The browser will not paint until it has the stylesheets, because painting with the wrong styles then correcting would flash. That flash has a name, FOUC.
  5. Layout computes a box for every rendered node: x, y, width, height, in a single tree walk, honouring the box model and the layout mode of each parent.
  6. Paint turns boxes into a list of drawing commands: fill this rectangle, draw this glyph run, clip here.
  7. Composite runs those command lists into layers, often on the GPU from Chapter 22, and stacks them. Scrolling and CSS transforms usually only touch this stage, which is why they are smooth.
  8. An event has three phases: capture, travelling down from the document to the target; target; then bubble, travelling back up. Most code only ever uses the bubble phase.
  9. stopPropagation() halts travel. preventDefault() cancels the browser’s built-in reaction, such as following a link. They are different and beginners confuse them constantly.

TECHNICAL35.5.5 the engineer’s version#

  1. DOM Level 1 became a W3C Recommendation in October 1998. The DOM is now a WHATWG Living Standard, and it is a language-independent API, not a JavaScript feature.
  2. The critical rendering path is the minimum set of resources needed for first paint: the HTML, the render-blocking CSS, and any parser-blocking JavaScript.
  3. Rendering engines in production: Blink (Chrome, Edge, Opera, forked from WebKit on 3 April 2013), WebKit (Safari), Gecko (Firefox).
  4. Style resolution is per element per property; matching selectors right to left is the standard implementation trick that makes descendant selectors cheap enough.
  5. Reflow cost is superlinear in tree depth and node count. Modern engines do partial layout, but a change to a flow-root ancestor still invalidates its whole subtree.
Change Triggers Relative cost
width, top, font-size layout, paint, composite highest
background-color, color paint, composite medium
transform, opacity composite only lowest
adding a class with all three full pipeline highest
  1. Animate transform and opacity. Animating left and width forces layout on every frame and will not hold 60 fps on a mid-range phone.
  2. A frame budget at 60 Hz is 16.7 ms; at 120 Hz it is 8.3 ms. Chapters 21 and 22 traced that budget from key press to photons.
  3. requestAnimationFrame schedules a callback just before the next paint, which is the correct hook for animation. setTimeout(fn, 16) is not.
  4. content-visibility: auto and contain let you tell the engine that a subtree cannot affect the outside, allowing it to skip work entirely.
  5. The DOM API is genuinely slow relative to plain objects, mostly because each call crosses from the JavaScript engine into the C++ engine. Batch changes with DocumentFragment or by building a string once.
  6. Tools: Performance panel with the Frames and Main tracks, the “Layout Shift Regions” and “Paint flashing” overlays in Rendering, and the PerformanceObserver API for long-animation-frame entries.

WORDS35.5.6 remember these#

  1. DOM — a live tree of the page in memory — the Document Object Model, a language-neutral object representation defined by WHATWG.
  2. CSSOM — the same idea for styles — the parsed stylesheet object model used during style resolution.
  3. Render tree — only the things that get drawn — the tree of styled boxes, excluding display: none and including generated content.
  4. Reflow — redo the positions — layout invalidation and recomputation of box geometry; also called relayout.
  5. Repaint — redo the colours — rasterization of the display list without a geometry pass.
  6. Composite — stack the finished layers — GPU-assisted assembly of rasterized layers into the final frame.
  7. Bubbling — the event travels outward — phase 3 of DOM event dispatch, from target up to the root.
  8. Delegation — one listener for many children — attaching a handler to a common ancestor and using event.target.

35.6 The browser as a platform#

PLAIN35.6.1 in simple words#

  1. A browser is not a document viewer any more. It is an operating system for web pages.
  2. It gives your page a set of built-in services, the same way an operating system gives a program files, memory and the network.
  3. It can store data on the user’s disk in three different ways, for different sizes and purposes.
  4. It can fetch more data from a server at any time, without loading a new page.
  5. It can run your code on extra threads, so slow work does not freeze the screen.
  6. It can keep a small program running in the background that answers requests even with no network, which is how a web app works offline.
  7. It can hold a two-way conversation with a server, where the server speaks first, instead of only answering questions.
  8. It can connect two users’ browsers directly to each other, for calls and video.
  9. It can draw arbitrary graphics and run code on the graphics chip.
  10. And it does all this inside a sandbox: a locked room where the page can touch only what it has been given, and must ask permission for the rest.

PLAIN35.6.2 a picture in your head#

  1. Think of a serviced office you rent by the hour.
  2. You get a desk, a locked drawer, a phone and a window. That is the platform: storage, network, screen.
  3. You cannot walk into another tenant’s office. The doors are locked and the locks are not yours to change. That is the same-origin rule.
  4. You cannot open the fire door to the roof without asking the building manager first, and the manager asks you in front of the tenant. That is the permission prompt for camera, microphone and location.
  5. There are two small back rooms you can hire someone to work in, out of sight of your desk. One does heavy calculation. One sits by the post slot and answers deliveries even when you have gone home.
  6. Those are web workers and service workers.

Where this comparison breaks:

  1. An office tenant can carry things down the corridor by hand. A page cannot reach another origin’s storage at all, by any route, including through the user’s own copy of the browser.
  2. And a real building manager can be persuaded. The sandbox is enforced by the browser’s own code, and cannot be talked around by the page.

PLAIN35.6.3 a worked example#

  1. A small task: fetch a list, cache it, and survive going offline.
// 1. Ask the server for JSON
const res = await fetch("/api/books", {
  headers: { "Accept": "application/json" },
});
if (!res.ok) throw new Error("HTTP " + res.status);
const books = await res.json();

// 2. Keep a copy the user can read while offline
localStorage.setItem("books", JSON.stringify(books));
  1. Note res.ok. fetch does not reject on a 404 or a 500. It only rejects on a network failure. Forgetting this is one of the most common bugs.
  2. Now the storage choices, with real limits.
Store Holds Typical limit Sent to server
Cookie small strings 4 KB each yes, every request
localStorage strings about 5 MB no
sessionStorage strings, per tab about 5 MB no
IndexedDB structured objects large, quota-based no
Cache Storage whole responses quota-based no
  1. localStorage is synchronous. A 2 MB read blocks the single thread, and the page freezes for that time. Do not use it for anything large.
  2. IndexedDB is asynchronous and can hold hundreds of megabytes, subject to a quota that is usually a share of free disk space.
  3. Cookies travel with every matching request automatically. That is why they suit session identifiers and suit nothing else.
  4. A minimal service worker turns that fetch into an offline-capable one.
// sw.js
self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then(hit =>
      hit || fetch(event.request)
    )
  );
});
  1. Now the browser asks the service worker before it asks the network. If the worker has a copy, the page loads with the aeroplane mode switch on.

PLAIN35.6.4 what is really happening inside#

  1. The same-origin policy is the core rule. An origin is the triple of scheme, host and port. https://a.com and https://a.com:8443 are different origins, and so are http and https versions of one host.
  2. Code from one origin cannot read the DOM, cookies or storage of another. It can still cause requests to another origin, which is why CSRF exists.
  3. CORS is the controlled exception. The server adds a response header saying which origins may read its answers. The browser enforces it. The rule lives with the server, not the page.
  4. A web worker is a separate thread with its own memory and no access to the DOM. You talk to it by sending messages that are copied, not shared.
  5. Use one for parsing a large file, image processing, or cryptography, so the main thread stays free to draw at 60 frames per second.
  6. A service worker is a special worker that sits between the page and the network, like a programmable proxy on the user’s own machine.
  7. It survives page closes, wakes for push messages, and must be served over HTTPS, because a hostile one would be permanent.
  8. WebSockets solve a real problem. Plain HTTP is ask-and-answer, so a server with news cannot speak first.
  9. The old workaround was polling: ask every few seconds, usually to be told nothing happened. Wasteful in requests, battery and latency.
  10. A WebSocket starts life as an ordinary HTTP request with an Upgrade header, then the same TCP connection becomes a two-way message pipe that stays open.
  11. WebRTC goes further and connects two browsers directly, so audio and video do not pass through your server. Your server only helps them find each other.

TECHNICAL35.6.5 the engineer’s version#

  1. Same-origin policy dates to Netscape Navigator 2.02 in 1995. CORS is defined in the WHATWG Fetch Standard. Preflight uses an OPTIONS request when the method or headers are non-simple.
  2. fetch() shipped from 2015 and replaced XMLHttpRequest, which came from Microsoft Outlook Web Access around 1999 and reached IE 5 in 1999. The term AJAX was coined by Jesse James Garrett in February 2005.
  3. WebSocket is RFC 6455, December 2011. Handshake is HTTP Upgrade: websocket; frames then carry a 2 to 14 byte header, versus roughly 500 to 800 bytes of HTTP headers per polled request.
Technique Direction Overhead Best for
Polling client asks high, constant rare updates
Long polling client waits medium legacy fallback
SSE server to client low, text only feeds, progress
WebSocket both ways lowest per message chat, games
WebRTC data peer to peer lowest latency calls, screen share
  1. Server-Sent Events use the text/event-stream MIME type, reconnect automatically, and are part of the HTML Living Standard. They are one-way and much simpler to operate than WebSockets.
  2. WebRTC was open-sourced by Google in May 2011 and became a W3C Recommendation on 26 January 2021. It needs STUN and usually TURN servers to traverse NAT, which Chapter 24 described. TURN relays cost real bandwidth.
  3. Graphics: Canvas 2D is immediate-mode drawing. WebGL 1.0 shipped March 2011 and is based on OpenGL ES 2.0; WebGL 2.0 (2017) on OpenGL ES 3.0. WebGPU shipped in Chrome 113 in May 2023, in Safari 26 in 2025, and gives compute shaders and a modern binding model.
  4. Storage quota is not fixed by specification. Chrome allows an origin up to about 60 percent of total disk in practice; Safari applies stricter eviction. This is an implementation detail and changes between versions.
  5. Permissions requiring an explicit user prompt: camera, microphone, geolocation, notifications, clipboard read, MIDI, Bluetooth, USB, and persistent storage. Most also require a secure context (HTTPS or localhost).
  6. Powerful features are gated three ways: secure context, transient user activation (a real click within a few seconds), and Permissions Policy headers set by the site.
  7. Tools: DevTools Application panel shows every store, service worker state, and quota usage; chrome://webrtc-internals shows live peer connections.

WORDS35.6.6 remember these#

  1. Sandbox — a locked room for the page — an isolated execution environment with mediated access to host resources.
  2. Origin — the identity of a site — the tuple of scheme, host and port used for all isolation decisions.
  3. CORS — the server’s note saying who may read this — Cross-Origin Resource Sharing headers defined by the Fetch Standard.
  4. Service worker — a proxy of your own on the user’s machine — an event-driven worker intercepting fetches, enabling offline and push.
  5. Web worker — a second thread with no screen access — a dedicated worker with an isolated global scope and message-passing only.
  6. WebSocket — a phone line instead of letters — RFC 6455 full-duplex framed messaging over a single upgraded TCP connection.
  7. IndexedDB — a real database in the browser — a transactional object store with indexes and an asynchronous API.

35.7 Frontend frameworks, explained rather than listed#

PLAIN35.7.1 in simple words#

  1. Here is the problem a framework exists to solve.
  2. Your page shows information. That information changes. The page must change to match, everywhere it appears, every time.
  3. Doing that by hand means writing instructions: find this element, change its text, add this row, remove that one, toggle this class.
  4. That style is called imperative: you say the steps.
  5. With ten pieces of information it is fine. With two hundred, in a page where any change can affect any other, it becomes impossible to keep straight.
  6. A framework lets you write declarative code instead: you describe what the page should look like for the current data, and the framework works out the steps to get there.
  7. The second big idea is the component: one self-contained piece of the page with its own markup, styling and behaviour, that you can use many times.
  8. A page becomes a tree of components, the way a house is a set of rooms rather than one giant space.
  9. That is all a framework is: a way to say “screen equals function of data”, plus a way to cut a screen into reusable pieces.

PLAIN35.7.2 a picture in your head#

  1. Imagine a departures board at a station, made of physical letter tiles.
  2. The imperative way: a person with a ladder, told “change platform 4 from 12 to 9, and change the 14:05 to Delayed, and remove the 13:50 row”.
  3. Every instruction must be right, in the right order, or the board lies.
  4. The declarative way: you hand over a printed sheet showing what the board should say now. A machine compares it with what the board says and changes only the tiles that differ.
  5. You never think about tiles. You think about the correct board.
  6. The comparing step is called reconciliation, and the sheet you hand over is the idea behind a virtual DOM.

Where this comparison breaks:

  1. The station board has fixed rows. A web page has a shifting tree, so the comparison needs hints about identity. That is what the key attribute is for, and getting keys wrong causes real bugs.
  2. And comparison is not free. Some frameworks skip it entirely by working out at build time exactly which tiles a given change touches.

PLAIN35.7.3 a worked example#

  1. The same counter, three ways.
// Imperative: plain DOM
let n = 0;
btn.addEventListener("click", () => {
  n = n + 1;
  out.textContent = "Count: " + n;
});
// Declarative: React
function Counter() {
  const [n, setN] = useState(0);
  return (
    <button onClick={() => setN(n + 1)}>
      Count: {n}
    </button>
  );
}
<!-- Declarative: Svelte 5 -->
<script>
  let n = $state(0);
</script>
<button onclick={() => n++}>Count: {n}</button>
  1. In the first, you own the update. In the other two, you own the description and the tool owns the update.
  2. With one counter the first is shorter. With a form of forty fields that feed a summary, a chart and a save button, the first becomes a maze.
  3. What React does when setN runs: it calls Counter() again, gets a new description, compares it with the old one, sees only the text differs, and changes that one text node. It does not rebuild the button.
  4. What Svelte does: at build time the compiler already worked out that n appears in exactly one text position, so it emits code that assigns to that text node directly. There is no comparison at runtime.

PLAIN35.7.4 what is really happening inside#

  1. State is the data that can change. Props are the data a parent hands to a child. A component re-renders when its own state or its props change.
  2. A virtual DOM is a plain tree of ordinary JavaScript objects describing the wanted output. Cheap to build, cheap to throw away.
  3. Reconciliation walks the old tree and the new tree together. Same type at the same position means update in place. Different type means destroy and rebuild. Lists are matched by key.
  4. Using an array index as a key is the classic bug: delete the first row and every key shifts, so the framework updates the wrong rows and typed input jumps to the wrong place.
  5. State management is the question of where shared data lives. Local state inside a component is easy. Data that four distant components need is not.
  6. The escalation ladder is: local state, then lift state to a common parent, then a context or provider, then a dedicated store, then a server-state library that caches responses.
  7. Hydration happens when HTML is rendered on the server for speed, then the same components run in the browser to attach event handlers to the existing markup. The page looks ready before it is interactive.
  8. The honest version: hydration is a workaround, not a triumph. It ships the data twice, once as HTML and once as JSON, and it is why some server-rendered pages feel dead for a second. Islands, partial hydration and React Server Components are all attempts to send less.

TECHNICAL35.7.5 the engineer’s version#

  1. React was created at Facebook by Jordan Walke and open-sourced in May 2013 at JSConf US. Facebook renamed itself Meta in October 2021. React 19 is current, with 19.2 published in 2026.
  2. JSX is a syntax extension compiled to function calls; it is not HTML and not part of the language. class becomes className because class is reserved.
  3. React uses one-way data flow: data goes down through props, changes go up through callbacks. Hooks arrived in React 16.8 (February 2019) and replaced class components as the normal style.
  4. The core hooks are useState, useEffect, useMemo, useCallback, useRef and useContext. Rules: only at the top level, only in components or other hooks, because hook identity is positional.
  5. useEffect is for synchronizing with something outside React, not for deriving data. Overuse of effects is the most common source of React bugs.
Framework First release Approach Trade-off
React May 2013, Meta virtual DOM, hooks huge ecosystem, more rerenders
Angular Sept 2016 (v2) full framework, DI batteries included, steep start
Vue Feb 2014, Evan You reactive proxies gentle curve, smaller job market
Svelte Nov 2016, R. Harris compiler, runes least runtime code, smaller ecosystem
Solid 2018, R. Carniato fine-grained signals very fast, small community
  1. AngularJS (October 2010, Misko Hevery at Google) and Angular 2 onward (14 September 2016) are different frameworks with a shared name. Angular uses TypeScript, RxJS and dependency injection, and now ships version 22.
  2. Vue 3 (September 2020) uses ES Proxy-based reactivity and a composition API. Svelte 5 (19 October 2024) introduced runes, replacing its label-based reactivity. Solid uses signals with no component rerenders at all.
  3. Signals, which Solid popularized, have now been adopted by Angular, Vue, Svelte and Preact, and a TC39 signals proposal exists. Experts disagree on whether React should adopt them; React’s team argues its compiler achieves the same result without changing the model.
  4. Meta-frameworks add routing, server rendering and data loading: Next.js for React (version 16 in 2026), Nuxt for Vue, SvelteKit, Angular with its own SSR, Remix and Astro.
  5. When you do not need a framework: content sites, marketing pages, blogs, documentation, forms with a handful of fields, and anything where the server can render the HTML and a hundred lines of plain JavaScript will do.
  6. A framework costs 40 to 150 KB of JavaScript before your own code, plus a build step, plus upgrade work forever. Spend that only when interactivity genuinely earns it.

WORDS35.7.6 remember these#

  1. Declarative — describe the result — express the target state and delegate the transition to the runtime.
  2. Component — one reusable piece of screen — an encapsulated unit of template, state and behaviour with a props interface.
  3. Virtual DOM — a cheap paper copy of the page — an in-memory tree diffed against the previous render to compute minimal DOM mutations.
  4. Reconciliation — comparing old and new — the diffing algorithm, keyed by element type and key attribute.
  5. Hydration — waking up server HTML — attaching client event listeners and state to markup already present in the document.
  6. Signal — a value that knows who reads it — a fine-grained reactive primitive that updates only its dependents.

35.8 Build tooling: why frontend code needs a build step#

PLAIN35.8.1 in simple words#

  1. Twenty years ago you wrote a .js file and put it on a server. Done.
  2. Today most projects run a program over the code first, and ship the result. That program is the build step.
  3. There are four honest reasons for it.
  4. One, your code is split across hundreds of small files, and loading hundreds of files is slow. So they are joined into a few. That is bundling.
  5. Two, you write modern syntax, and some browsers do not understand all of it. So it is rewritten into older syntax. That is transpiling.
  6. Three, some files are not JavaScript at all: JSX, TypeScript, Svelte components, Sass. Something must convert them.
  7. Four, shipping fewer bytes makes pages faster, so names are shortened, spaces removed and unused code deleted.
  8. Nothing here is magic. The build step is a translator and a packer.

PLAIN35.8.2 a picture in your head#

  1. Think of moving house.
  2. Your belongings are spread through many rooms in many small containers. That is your source code: many files, each doing one thing, easy for you to find.
  3. You would not carry two hundred small boxes to the van one at a time. You pack them into a few large cartons. That is bundling.
  4. Anything broken or never used gets thrown out rather than moved. That is tree shaking.
  5. Air is squeezed out and clothes are vacuum-packed. That is minification.
  6. And you keep an inventory sheet saying which carton each item is in, so that when something is missing at the far end you can trace it back. That is the source map.

Where this comparison breaks:

  1. When you move house, you unpack. The browser never unpacks: it runs the packed form. The source map only exists for you, in the debugger.
  2. And you can add a room after moving in. Bundles are fixed at build time, which is why code splitting has to be planned, not discovered.

PLAIN35.8.3 a worked example#

  1. A tiny package.json, which is the project’s identity card.
{
  "name": "bookshop-web",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "vitest run"
  },
  "dependencies": { "react": "^19.2.0" },
  "devDependencies": { "vite": "^8.2.0" }
}
  1. dependencies are needed to run. devDependencies are needed only to build and test, and never reach the browser.
  2. ^19.2.0 means “19.2.0 or any later 19.x”. That range is why two people can install the same project on the same day and get different code.
  3. The lock file, package-lock.json or pnpm-lock.yaml, records the exact version and content hash of every package that was actually installed.
  4. Commit the lock file. Without it your builds are not reproducible, and a working build today can break tomorrow with no change from you.
  5. Tree shaking, shown concretely:
// utils.js exports 40 functions
import { formatDate } from "./utils.js";
  1. Because ES modules declare their imports statically, the bundler can prove the other 39 are unreachable and drop them. CommonJS require is dynamic, so it usually cannot.
  2. Code splitting turns one big file into several loaded on demand:
const Chart = lazy(() => import("./Chart.jsx"));
  1. Now the chart’s code downloads the first time a user opens the chart, not on every page load.

PLAIN35.8.4 what is really happening inside#

  1. A bundler starts at an entry file, follows every import, and builds a graph of modules. Then it writes that graph out as one or more files.
  2. A transpiler parses source into a syntax tree, rewrites nodes, and prints new source. Babel does this for JavaScript syntax.
  3. Transpiling handles syntax. Missing functions need a polyfill, which is real code implementing the missing feature. These are different jobs.
  4. TypeScript adds a type layer on top of JavaScript. It checks your code, then erases every type annotation. Nothing is checked at runtime.
  5. That erasure is the key point people miss. TypeScript protects you while you write. It gives you nothing at all against bad data arriving from a network request. You still have to validate.
  6. Minification renames local variables to single letters, removes whitespace and comments, and simplifies expressions. Typical saving is 30 to 60 percent before compression.
  7. A source map is a separate file mapping every position in the output back to a line and column in your source, so the debugger can lie helpfully.
  8. node_modules is where installed packages live, honestly: a very large folder containing your dependencies and all of their dependencies, often tens of thousands of files and hundreds of megabytes.
  9. It is not a virus and it is not waste by design. It is the visible cost of a culture of very small packages. It should never be committed to git, and it is always rebuildable from package.json plus the lock file.

TECHNICAL35.8.5 the engineer’s version#

  1. npm launched in January 2010, written by Isaac Schlueter, and is now operated by GitHub. The registry holds well over three million packages.
  2. Alternatives: Yarn (2016), pnpm (2017, which hard-links a global store and cuts disk use sharply), and Bun’s built-in installer (2022).
  3. Semantic versioning is MAJOR.MINOR.PATCH. ^1.2.3 allows minor and patch updates, ~1.2.3 allows patch only, and an exact pin allows neither. This is a convention, and packages break it regularly.
Tool First release Written in Role
webpack 2012, T. Koppers JavaScript bundler, loaders
Rollup 2015, R. Harris JavaScript library bundler
Babel 2014, S. McKenzie JavaScript transpiler
esbuild 2020, E. Wallace Go very fast bundler
Vite 2020, Evan You JS plus Rust dev server, build
SWC 2019 Rust transpiler, minifier
  1. Vite’s development server does not bundle. It serves native ES modules and transforms each file on request, so start-up is near instant regardless of project size. For production it bundles with Rollup.
  2. Speed differences are large and measurable: esbuild and SWC are commonly 10 to 100 times faster than Babel and webpack on the same input, because they are compiled languages with parallelism rather than single-threaded JavaScript.
  3. TypeScript was released by Microsoft on 1 October 2012, designed by Anders Hejlsberg. TypeScript 7, a native port of the compiler to Go, shipped in 2026 with roughly a tenfold type-checking speed-up.
  4. Supply chain risk is real. Compromised packages have shipped malware to millions of installs. Mitigations: lock files, npm audit, npm ci in CI, --ignore-scripts, and provenance attestations.
  5. Bundle budgets that hold up in practice: under 100 KB of compressed JavaScript for a content site, under 300 KB for a rich application. Measure compressed transfer size, not the raw file size.
  6. Tools: npx vite build --sourcemap, rollup-plugin-visualizer, Chrome DevTools Coverage panel to find unused bytes, npm ls <pkg> to find who pulled a package in.

WORDS35.8.6 remember these#

  1. Bundler — the packer — a tool that resolves a module graph and emits fewer, larger output files.
  2. Transpile — rewrite new syntax as old — source-to-source compilation between language versions.
  3. Polyfill — supply a missing function — runtime code implementing a standard API absent in the host.
  4. Tree shaking — drop code nobody uses — dead-code elimination over a static ES module graph.
  5. Source map — the inventory sheet for debugging — a JSON mapping from generated positions back to original source.
  6. Lock file — the exact recipe of what was installed — a resolved dependency tree with versions and integrity hashes.
  7. Code splitting — download it when needed — emitting separate chunks loaded by dynamic import().

35.9 The backend: what a server program actually does#

PLAIN35.9.1 in simple words#

  1. A server program is an ordinary program with one unusual habit: it never finishes.
  2. It opens a port, which is a numbered door on the machine, and waits.
  3. When a request arrives, it reads it, decides what it means, does the work, writes an answer, and goes back to waiting.
  4. Deciding what a request means is routing: matching the method and path, such as GET /books/12, to the piece of code that handles it.
  5. The piece of code that handles it is called a handler, or in some traditions a controller.
  6. Work that must happen on many requests, such as logging, checking who the user is, or parsing the body, is factored out into middleware: small steps a request passes through on the way in.
  7. There are two shapes of backend. One builds finished HTML pages and sends them. The other sends only data, usually JSON, and lets the frontend draw.
  8. The first is templating. The second is API-only. Both are fine, and mixing them is normal.

PLAIN35.9.2 a picture in your head#

  1. Think of a hospital reception.
  2. The door is the port. Anyone may walk in.
  3. Before anyone sees a doctor they pass a fixed sequence: security check, then registration, then the file is pulled, then vitals are taken. Every patient, every time, in that order. That is middleware.
  4. Reception then reads the complaint and sends the patient to the right department. That is routing.
  5. The specialist who actually treats them is the handler.
  6. The discharge letter handed back is the response.
  7. If registration fails, the patient never reaches the specialist, and is sent away at that step. That is middleware returning early, which is exactly how authentication is enforced.

Where this comparison breaks:

  1. A hospital treats patients one at a time per doctor. A server holds thousands of half-finished requests at once, interleaved.
  2. And a hospital remembers you between visits. HTTP does not. Every arrival is a stranger until a cookie or token proves otherwise.

PLAIN35.9.3 a worked example#

  1. The same tiny API, in Node with Express and in Python with FastAPI.
// server.js  -  Node with Express 5
import express from "express";
const app = express();

app.use(express.json());          // middleware
app.use((req, res, next) => {     // logging
  console.log(req.method, req.url);
  next();
});

app.get("/books/:id", async (req, res) => {
  const book = await db.findBook(req.params.id);
  if (!book) return res.status(404).json({ error: "no" });
  res.json(book);
});

app.listen(3000);
# main.py  -  Python with FastAPI
from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/books/{book_id}")
async def read_book(book_id: int):
    book = await db.find_book(book_id)
    if book is None:
        raise HTTPException(status_code=404)
    return book
  1. The shapes are the same: declare a path, name the variable part, do the work, return data or an error code.
  2. FastAPI reads the book_id: int annotation and rejects /books/abc with a 422 before your function runs. Express does not; you must check yourself.
  3. The request lifecycle, drawn:
TCP accept
  -> parse HTTP request line and headers
  -> middleware 1 (parse body)
  -> middleware 2 (log)
  -> middleware 3 (authenticate) -- may stop here
  -> router match: GET /books/:id
  -> handler runs, queries database
  -> serialize response, set status and headers
  -> write bytes, maybe keep connection alive

PLAIN35.9.4 what is really happening inside#

  1. Under the routing there is a socket, from Chapter 28. listen puts the process into the accepting state; the kernel queues incoming connections.
  2. Two families of design handle concurrency.
  3. Thread or process per request: each request gets its own worker, which is allowed to block. Simple to reason about. Costs memory per worker, roughly 0.5 to 8 MB of stack, so thousands of workers is expensive.
  4. Event loop: one thread handles many connections by never blocking, using the same idea as the browser event loop in section 35.4. Cheap per connection, but one slow synchronous function stalls everyone.
  5. Node.js uses the second. So do FastAPI with async handlers, Go’s runtime with very cheap goroutines, and modern Java with virtual threads.
  6. This is why a Node server is excellent for many connections doing little work each, such as an API or a chat gateway, and a poor choice for heavy number-crunching in the request path.
  7. The way out for Node is not to fight it: move heavy work to a worker thread, a separate service, or a background job queue.
  8. In every design, the database is usually the real bottleneck, not the language. A 40 ms query dwarfs a 0.4 ms difference in framework overhead.

TECHNICAL35.9.5 the engineer’s version#

  1. Node.js was released by Ryan Dahl in 2009 and presented at JSConf EU in November 2009. It pairs V8 with libuv for asynchronous input and output. Node 24 “Krypton” is the active LTS as of 2026; Node 26 became the current line in May 2026.
  2. Node is single-threaded for JavaScript but not single-threaded overall: libuv keeps a thread pool, four by default, for file system work, DNS and some crypto.
  3. Express began in 2010 by TJ Holowaychuk; Express 5.0 was released in 2024 after a very long wait. Fastify and Hono are faster modern alternatives. Deno (2018) and Bun (2022) are competing runtimes.
Stack Since Concurrency Strong at
Node.js 2009 event loop APIs, realtime, one language
Django 2005 threads, async admin, ORM, batteries
FastAPI Dec 2018 async, ASGI typed APIs, auto docs
Spring Boot Apr 2014 threads, virtual large enterprise systems
Go 2009, 1.0 2012 goroutines throughput, single binary
Rails July 2004 threads fast product iteration
Laravel June 2011 processes shared hosting, PHP shops
ASP.NET Core June 2016 async tasks Windows shops, strong tooling
  1. Django was created at the Lawrence Journal-World by Adrian Holovaty and Simon Willison and released publicly in July 2005; Django 6.1 is current. FastAPI was released by Sebastian Ramirez in December 2018.
  2. Ruby on Rails was extracted from Basecamp by David Heinemeier Hansson in July 2004 and popularized convention over configuration. Rails 8.1 is current in 2026.
  3. Go was announced by Google on 10 November 2009 (Robert Griesemer, Rob Pike, Ken Thompson) with 1.0 in March 2012; Go 1.26 is current. A goroutine starts at about 2 KB of stack, versus about 1 MB for an operating system thread.
  4. Spring Framework began in 2003 from Rod Johnson’s book; Spring Boot arrived in April 2014 and Spring Boot 4.1 is current in 2026. Java 25, released 16 September 2025, is the newest long-term-support release.
  5. PHP dates from 1995 (Rasmus Lerdorf); PHP 8.5 arrived November 2025. Laravel was released by Taylor Otwell in June 2011 and is now at version 13.
  6. .NET Core 1.0 shipped June 2016 and unified with .NET 5 in November 2020; .NET 10, released 11 November 2025, is the current long-term-support version.
  7. Honest guidance: pick the language your team already knows. Every framework in that table serves millions of users somewhere. The differences that matter are hiring, libraries and operational familiarity, not benchmarks.
  8. Tools: curl -i, httpie, ab and wrk for load, strace or dtruss for syscalls, and clinic.js or pprof for profiling.

WORDS35.9.6 remember these#

  1. Route — the address-to-code map — a match on HTTP method and path pattern dispatching to a handler.
  2. Middleware — a step every request passes through — a composable function in the request pipeline that may short-circuit the response.
  3. Handler — the code that answers — the terminal function producing a status, headers and body; also called a controller action.
  4. Templating — the server builds the HTML — server-side rendering of markup from data plus a template language.
  5. Event loop server — one worker, never waiting — non-blocking I/O multiplexed with epoll, kqueue or IOCP through libuv or similar.
  6. Port — a numbered door on a machine — a 16-bit TCP or UDP endpoint identifier bound by a listening socket.

35.10 Databases#

PLAIN35.10.1 in simple words#

  1. Programs forget everything when they stop. A database is the place data goes so it survives, and so many users can share it safely.
  2. You could use files. People do, and then discover the hard parts: two users writing at once, finding one record among ten million, and a crash halfway through an update.
  3. A database solves exactly those three problems: concurrency, fast search, and never being left half-done.
  4. The oldest and still best default is the relational database: data stored as tables of rows and columns, queried with a language called SQL.
  5. A table is one kind of thing, such as books. A row is one of them. A column is one fact about it.
  6. A primary key is the column that uniquely names a row. A foreign key is a column in one table holding the key of a row in another, which is how tables are joined together.
  7. NoSQL is a loose family of databases that gave up some of those rules for speed, scale or a friendlier shape. Sometimes that is the right trade. Often it is not.
  8. If you have no strong reason otherwise, start with PostgreSQL. This is not fashion; it is the advice most experienced engineers give.

PLAIN35.10.2 a picture in your head#

  1. Think of a library’s paper records before computers.
  2. One drawer holds a card per book: title, author number, year. Another drawer holds a card per author: number, name, country.
  3. The author’s name is written once, in the author drawer. The book card only stores the author’s number.
  4. That is normalization, and the reason for it is simple: if an author’s name is wrong, you fix one card, not four hundred.
  5. Looking up a book by title is fast because the drawer is sorted and you can jump to the right divider. That sorted arrangement is an index.
  6. Without an index you would read every card in the drawer. With one you touch about twenty. That gap is the whole reason indexes exist.
  7. And when a book moves between branches, both the “removed from A” and “added to B” cards must change together, or the book vanishes. Doing both or neither is a transaction.

Where this comparison breaks:

  1. Library cards do not lie to each other. Two clerks writing at the same moment can produce a state neither intended, which is why isolation levels exist and why they are subtle.
  2. And a card drawer holds thousands. A real index handles billions with roughly the same number of steps, because it is a tree, not a list.

PLAIN35.10.3 a worked example#

  1. Start with a badly designed table. One row per sale.
id book author author_country qty
1 Dune F. Herbert US 2
2 Emma J. Austen UK 1
3 Dune F. Herbert US 3
  1. Three faults. The author’s country is repeated; if it is wrong it is wrong in many places. You cannot record an author with no sales. And updating one row can leave the data disagreeing with itself.
  2. Normalized into three tables:
CREATE TABLE authors (
  id      SERIAL PRIMARY KEY,
  name    TEXT NOT NULL,
  country CHAR(2) NOT NULL
);

CREATE TABLE books (
  id        SERIAL PRIMARY KEY,
  title     TEXT NOT NULL,
  author_id INT NOT NULL REFERENCES authors(id)
);

CREATE TABLE sales (
  id      SERIAL PRIMARY KEY,
  book_id INT NOT NULL REFERENCES books(id),
  qty     INT NOT NULL CHECK (qty > 0),
  sold_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
  1. Now a real query: total copies sold per country, best first, ignoring countries with fewer than 10 sales.
SELECT a.country,
       SUM(s.qty) AS copies
FROM sales s
JOIN books   b ON b.id = s.book_id
JOIN authors a ON a.id = b.author_id
WHERE s.sold_at >= '2026-01-01'
GROUP BY a.country
HAVING SUM(s.qty) >= 10
ORDER BY copies DESC
LIMIT 5;
  1. JOIN follows the foreign keys to stitch the three tables back together. GROUP BY collapses many rows into one per country. WHERE filters rows before grouping; HAVING filters groups after. That difference is examined constantly and understood rarely.
  2. Now indexes, with real arithmetic. A sales table with 10 million rows.
  3. Without an index, finding rows for one book_id reads all 10,000,000 rows. At perhaps 2 million rows per second in memory, that is about 5 seconds.
  4. With a B-tree index, the rows are held in a sorted tree with a high branching factor. If each node holds about 200 keys, then 200^3 is 8 million and 200^4 is 1.6 billion.
  5. So four levels cover the table. The lookup reads four pages instead of ten million. That is roughly a million times less work, and it is why the answer comes back in under a millisecond.
CREATE INDEX idx_sales_book ON sales (book_id, sold_at);
EXPLAIN ANALYZE
SELECT * FROM sales WHERE book_id = 42;
  1. EXPLAIN prints the plan the database intends to use. Seq Scan means it is reading everything; Index Scan means it is using the tree. Adding ANALYZE actually runs it and reports real timings and row counts.
  2. The single most useful debugging habit in backend work is comparing the planner’s estimated rows with the actual rows. A large gap means the statistics are stale, and the plan is probably bad.

PLAIN35.10.4 what is really happening inside#

  1. ACID, one letter at a time.
  2. A, Atomicity. A transaction happens completely or not at all. Money leaving one account and arriving in another either both happen or neither does. A crash in between leaves no half-state.
  3. C, Consistency. The database never ends a transaction in a state that breaks the rules you declared: foreign keys, uniqueness, checks.
  4. I, Isolation. Concurrent transactions do not see each other’s unfinished work. How strictly is a setting, and that is the interesting part.
  5. D, Durability. Once the database says “committed”, the data survives an immediate power cut, because it was written to a log on disk and flushed.
  6. Isolation levels, weakest to strongest, and the anomaly each one stops:
Level Prevents Still allows
Read uncommitted nothing dirty reads
Read committed dirty reads non-repeatable reads
Repeatable read non-repeatable reads phantoms (in theory)
Serializable all of the above nothing, but retries
  1. A dirty read is seeing another transaction’s uncommitted change. A non-repeatable read is reading the same row twice and getting different values. A phantom is running the same query twice and getting a new row.
  2. PostgreSQL defaults to read committed and never permits dirty reads at all. Its repeatable read also prevents phantoms, because it uses snapshots. MySQL’s InnoDB defaults to repeatable read. These are implementation details worth checking for your engine.
  3. Connection pooling. Opening a database connection is expensive: a TCP handshake, authentication, and on PostgreSQL a new operating system process, costing several megabytes.
  4. So the application keeps a small set of open connections and lends them out. A pool of 10 to 30 usually serves far more concurrent users than that, because each request holds a connection for only milliseconds.
  5. The classic production failure is one pool per instance times many instances, exhausting the server’s connection limit. PgBouncer exists to sit in the middle and fix exactly that.

TECHNICAL35.10.5 the engineer’s version#

  1. The relational model comes from Edgar F. Codd’s 1970 paper at IBM, “A Relational Model of Data for Large Shared Data Banks”. SEQUEL was designed by Donald Chamberlin and Raymond Boyce in 1974 and renamed SQL.
  2. SQL was standardized by ANSI in 1986 and ISO in 1987; the current edition is SQL:2023. Every engine adds extensions, so portable SQL is a discipline.
  3. B-trees come from Rudolf Bayer and Edward McCreight in 1970, published 1972. Databases use B+ trees, where all values sit in the leaves and leaves are linked, making range scans cheap.
  4. ACID as a concept is Jim Gray’s, 1981; the acronym was coined by Theo Harder and Andreas Reuter in 1983.
  5. PostgreSQL descends from POSTGRES, begun by Michael Stonebraker at Berkeley in 1986; Postgres95 added SQL, and the name PostgreSQL dates from 1996. PostgreSQL 18 was released on 25 September 2025.
Type Example Shape Good at
Relational PostgreSQL 18 tables, SQL almost everything
Document MongoDB 8.3 JSON documents flexible schema
Key-value Redis 8.10 key to value cache, counters, queues
Wide-column Cassandra 5 partitioned rows huge write volume
Graph Neo4j nodes and edges relationship traversal
Search Elasticsearch inverted index full-text ranking
Time-series TimescaleDB time-partitioned metrics, sensors
  1. Origins: MongoDB from 10gen in 2009 (Dwight Merriman, Eliot Horowitz); Redis in 2009 by Salvatore Sanfilippo; Cassandra at Facebook in 2008, Apache in 2010; Neo4j in 2007; Elasticsearch in 2010 by Shay Banon.
  2. Redis changed licence in March 2024, which produced the Valkey fork under the Linux Foundation, then adopted AGPLv3 with Redis 8 in May 2025. If you use a managed cache, check which one you are actually running.
  3. CAP theorem, stated correctly. It was conjectured by Eric Brewer in a July 2000 keynote and proved by Seth Gilbert and Nancy Lynch in 2002.
  4. It says: when a network partition occurs, a distributed system must choose between consistency (every read sees the latest write) and availability (every request gets a non-error answer).
  5. The common misuse is to describe systems as “CP” or “AP” as a permanent personality, or to say you may “pick two of three”. You do not choose partitions; the network chooses them for you. C versus A is a choice made only during a partition, often per operation.
  6. Daniel Abadi’s PACELC (2012) is the honest extension: else, when there is no partition, you still trade latency against consistency.
  7. Five scenarios, and the honest answer for each.
Scenario Choose Why
Payments, orders PostgreSQL transactions decide correctness
Session cache Redis sub-ms reads, expiry built in
Product search Elasticsearch ranking, typo tolerance
Device telemetry Timescale time partitioning, retention
Social graph paths Neo4j multi-hop traversal
  1. Note what is missing: no scenario says “MongoDB because we do not know the schema yet”. PostgreSQL has a jsonb column type with indexing, so you can keep flexible documents inside a relational database and still have joins and transactions when you need them.
  2. Tools: psql, EXPLAIN (ANALYZE, BUFFERS), pg_stat_statements for the slowest queries, pgbench for load, and the slow query log in MySQL.

WORDS35.10.6 remember these#

  1. Primary key — the unique name of a row — a column set with a uniqueness and not-null constraint identifying each tuple.
  2. Foreign key — a pointer to another table’s row — a referential integrity constraint enforced on insert, update and delete.
  3. Normalization — store each fact once — decomposing relations to remove update anomalies, usually to third normal form.
  4. Index — the sorted shortcut — an auxiliary B+ tree or hash structure mapping key values to row locations.
  5. Transaction — all or nothing — a unit of work bounded by BEGIN and COMMIT with ACID guarantees.
  6. Isolation level — how much of others’ work you may see — the setting choosing which concurrency anomalies are permitted.
  7. CAP theorem — under a network split, pick consistency or answers — during a partition a system cannot be both linearizable and available.
  8. Connection pool — a reused set of open lines — a bounded cache of database connections shared across requests.

35.11 ORMs and data access#

PLAIN35.11.1 in simple words#

  1. Your program thinks in objects. The database thinks in tables. Something has to translate.
  2. An ORM, an Object-Relational Mapper, is a library that does that translation for you.
  3. You write user.orders in your language and it writes SQL, runs it, and turns the rows back into objects.
  4. The gain is real: less repetitive code, fewer typos, and safety against SQL injection because values are sent separately from the query text.
  5. The cost is also real: it is easy to write one innocent line that causes hundreds of database round trips.
  6. An ORM usually brings migrations too: small, ordered, versioned scripts that change the shape of the database and are applied in the same order on every machine.

PLAIN35.11.2 a picture in your head#

  1. An ORM is a translator at a meeting between two people who share no language.
  2. For ordinary sentences the translation is faithful and saves everyone effort.
  3. But if you ask an ambiguous question, the translator will produce something grammatical and wrong, and you will not notice because you cannot read the other language.
  4. So a good engineer learns enough of the other language to check the translation, even when using the translator daily.

Where this comparison breaks:

  1. A human translator gets tired. An ORM is perfectly consistent, which means a bad pattern is repeated identically ten thousand times a second.

PLAIN35.11.3 a worked example#

  1. The N+1 query problem, the single most common performance bug in backend work.
orders = Order.objects.filter(status="paid")   # 1 query
for o in orders:
    print(o.customer.name)   # 1 query per order
  1. With 200 orders that is 1 + 200 = 201 round trips. At 2 ms each, that is about 0.4 seconds of pure waiting, for data one query could fetch.
  2. The fix is to tell the ORM to fetch the related rows in advance:
orders = (Order.objects
          .filter(status="paid")
          .select_related("customer"))   # 1 query, joined
  1. Now it is one query with a JOIN. Roughly 2 ms instead of 400 ms.
  2. The same fix has different names in different tools: select_related and prefetch_related in Django, include in Prisma, joinedload in SQLAlchemy, includes in Rails, with in Laravel.
  3. The way to catch it is to log queries per request in development. If one page view issues 300 queries, you have found it.

PLAIN35.11.4 what is really happening inside#

  1. The ORM keeps a map from classes to tables and from attributes to columns, plus rules for relationships.
  2. Accessing a relationship attribute that has not been loaded triggers a lazy load: a fresh query, issued right there in the loop. That is where N+1 comes from.
  3. Many ORMs also keep an identity map and a unit of work, so the same row read twice gives the same object and changes are written in one flush at the end.
  4. Migrations work by recording which scripts have run in a table inside the database itself. On deploy, the tool compares that list with the files on disk and applies the missing ones in order.
  5. Never edit an applied migration. Write a new one. The applied list is the shared history and rewriting it desynchronizes environments.
  6. Raw SQL is better when the query is the point: complex reporting, window functions, recursive queries, bulk updates, and anything where you need to control the plan.
  7. Modern practice is to use the ORM for the ordinary 90 percent and drop to SQL for the 10 percent that matters, rather than choosing one forever.

TECHNICAL35.11.5 the engineer’s version#

  1. The term object-relational impedance mismatch describes the structural gap: inheritance, identity and associations have no direct relational equivalent.
  2. Notable tools and dates: Hibernate for Java (2001, Gavin King), Rails ActiveRecord (2004), Django ORM (2005), SQLAlchemy (2006, Michael Bayer), Entity Framework (2008), Eloquent (2011), Prisma (2019, now version 7), Drizzle (2022).
  3. Two design patterns, from Martin Fowler’s 2002 book Patterns of Enterprise Application Architecture: Active Record, where a row and its behaviour are one object, and Data Mapper, where they are separate.
  4. Query builders such as Knex, jOOQ and Drizzle sit between raw SQL and a full ORM: typed composition, no object graph, no lazy loading, so no N+1.
  5. Parameterized queries prevent SQL injection because the driver sends the query text and the values on separate paths, so a value can never become syntax. String concatenation into SQL is the vulnerability.
  6. Migration tools: Alembic, Django migrations, Flyway, Liquibase, Prisma Migrate, Rails Active Record migrations, EF Core migrations.
  7. Zero-downtime schema change is a separate discipline: add a nullable column, backfill in batches, start writing both, switch reads, then drop the old column, across several deploys. Never rename in one step.
  8. Tools: django-debug-toolbar, SQLAlchemy echo=True, Prisma’s query event log, and pg_stat_statements to find the query issued most often rather than the slowest single one.

WORDS35.11.6 remember these#

  1. ORM — a translator between objects and tables — a mapping layer generating SQL and materializing rows as domain objects.
  2. N+1 — one query, then one more per result — repeated lazy loads inside an iteration, fixed by eager loading or a join.
  3. Migration — a versioned change to the schema — an ordered, recorded script applied idempotently across environments.
  4. Lazy loading — fetch it only when touched — deferred query execution on first access to an unloaded association.
  5. Parameterized query — values sent apart from the text — placeholders bound by the driver, preventing SQL injection by construction.

35.12 Authentication and sessions#

PLAIN35.12.1 in simple words#

  1. Authentication is proving who you are. Authorization is deciding what you are allowed to do once you are known.
  2. They are two different questions, they fail in two different ways, and mixing them up is one of the most common security bugs there is.
  3. A site must never keep your password the way you typed it. If a site can email your old password back to you, it stored it wrongly.
  4. Instead it stores a scrambled fingerprint of the password, produced by a one-way function called a hash.
  5. One-way means you can go from password to fingerprint easily, and from fingerprint back to password essentially never.
  6. It also mixes in a random extra value called a salt, different for every single user, so two people with the same password get different fingerprints.
  7. And the hash used for passwords is made deliberately slow. That sounds mad, and it is the entire point.
  8. Once you have logged in, the site has to remember you on the next click, because HTTP forgets everything between one request and the next.
  9. There are two ways to remember. Keep a note on the server and hand you a ticket number: that is a session. Or hand you a sealed pass that you carry and the server does not store: that is a token.
  10. When you click “sign in with Google”, you never give Google’s password to the site you are visiting. A protocol called OAuth 2.0 arranges that carefully.

PLAIN35.12.2 a picture in your head#

  1. Think of a cloakroom at a theatre, and a wristband at a festival.
  2. A session is the cloakroom ticket. Your coat stays with the cloakroom staff and you carry nothing but a number.
  3. The number means nothing on its own. Only the staff’s book turns number 47 into “the long grey coat belonging to that person”.
  4. If you lose the ticket, the staff can cross out number 47 in the book instantly, and it stops working everywhere, at once.
  5. A token is the festival wristband. Your name is printed on it and it carries a tamper-proof seal that any gate can check without phoning anyone.
  6. That makes gates fast and independent. Ten gates can check ten thousand wristbands with no shared book at all.
  7. But if you are thrown out of the festival, the wristband is still on your wrist. It keeps working until it expires, unless every gate is handed a list of banned wristbands - and a shared list is a cloakroom book again.

Where this comparison breaks:

  1. A photograph of a wristband gets you nowhere at a real gate, because a human looks at your face. A copied token is not a picture of you. To the server it simply is you, completely, until the moment it expires.
  2. A real cloakroom holds a few hundred coats in one room. A session store holds millions of tickets across many servers, which is why real systems keep it in Redis or a database rather than in one server’s memory.

PLAIN35.12.3 a worked example#

  1. Here is a bcrypt password record, the kind that sits in a users table. It is one string that carries four things at once.
$2b$12$KIXqf8vN1sT.u2WcQ7pOyeR3hZ...

 $2b$   the algorithm and version (bcrypt)
 12$    the cost factor: 2^12 = 4096 key rounds
 KIX... the first 22 characters are the salt
 R3h... the rest is the hash itself
  1. Notice there is no separate salt column. The salt travels inside the record, because it is not a secret. It only has to be unique and unpredictable.
  2. Checking a login runs the same function again with the stored salt and cost, then compares the results. It never reverses anything.
  3. The cost factor is a power of two, so each step up doubles the work. Rough times on one ordinary 2025 server core:
Cost Rounds Time per hash
8 256 about 6 ms
10 1,024 about 25 ms
12 4,096 about 100 ms
14 16,384 about 400 ms
  1. Why slow is the goal: an attacker who steals the table wants to try billions of guesses. At cost 12 a single core manages about 10 guesses a second.
  2. A fast hash such as SHA-256 runs at billions of guesses a second on a GPU. The same stolen table would fall in hours instead of centuries.
  3. The salt kills the other attack. Without salts, an attacker hashes the top ten million common passwords once and looks up every user at the same time.
  4. With a unique salt per user, that precomputed table is worthless, and the attacker must redo all the work separately for every single account.
  5. Now authorization, with a worked role-based example. Three roles, four permissions, and one rule table.
Role Read post Edit any post Delete user
reader yes no no
editor yes yes no
admin yes yes yes
  1. A user is given roles. A role is given permissions. Code never asks “is this user an admin”; it asks “does this user have post.edit”.
ROLE_PERMS = {
  "reader": {"post.read"},
  "editor": {"post.read", "post.edit"},
  "admin":  {"post.read", "post.edit", "user.delete"},
}

def can(user, perm):
    return any(perm in ROLE_PERMS[r] for r in user.roles)
  1. The difference matters when the rules change. Adding a “moderator” role who may edit but not delete is one line in the table.
  2. If the code had said if user.is_admin in forty places, the same change is forty edits and you will miss one.
  3. Authentication failed gives HTTP 401. Authorization failed gives HTTP 403. The 401 name in the standard is “Unauthorized”, which is a historical mistake; it really means unauthenticated.

PLAIN35.12.4 what is really happening inside#

  1. Session flow, step by step. You POST your email and password over HTTPS.
  2. The server looks up the row, runs the hash function with the stored salt, and compares. It uses a constant-time comparison so timing leaks nothing.
  3. On success it creates a random session identifier, at least 128 bits from a cryptographically secure generator, and stores it with your user id.
  4. It sends that identifier back in a Set-Cookie header. The browser stores it and attaches it to every later request to that site automatically.
  5. The cookie carries flags that do the real security work. HttpOnly hides it from JavaScript. Secure sends it only over HTTPS. SameSite controls whether other sites can cause it to be sent.
  6. Logging out deletes the server record. The ticket is dead immediately.
  7. Token flow, step by step. Same login, but the server returns a JWT, a JSON Web Token, and stores nothing.
  8. A JWT is three base64url-encoded parts joined by dots: a header saying which algorithm, a payload of claims such as user id and expiry, and a signature.
  9. The signature is computed by the server over the first two parts using a secret key. Anyone can read a JWT; only the key holder can forge one.
  10. Read that again, because it is the mistake people make: a JWT is signed, not encrypted. Never put anything private in the payload.
  11. To check a request, the server verifies the signature and the expiry. No database lookup at all, which is why tokens scale across many servers.
  12. The cost is revocation. There is no record to delete, so a stolen token works until it expires. That is why access tokens are given minutes, not days.
  13. The standard fix is a pair: a short access token, plus a long-lived refresh token kept in an HttpOnly cookie, which can be revoked and rotated.
  14. Now the delegated login flow. The point of OAuth 2.0 is that the site you are visiting never sees your Google password, and never can.
  15. The four parties: you (the resource owner), the site (the client), Google’s login server (the authorization server), and Google’s API (the resource server).
 You        Site (client)   Auth server     API
  |              |               |           |
  |--click sign in-->|           |           |
  |              |--redirect---->|           |
  |<---------- login page -------|           |
  |--password + consent--------->|           |
  |              |<--code (in URL redirect)--|
  |              |               |           |
  |              |--code+secret+verifier---->|
  |              |<--access token + id token-|
  |              |               |           |
  |              |--token------------------->|
  |              |<--your profile data-------|
  |<--logged in--|               |           |
  1. Step 1: the site sends your browser to the authorization server with its client id, the scopes it wants, a redirect address, a random state value, and a code challenge.
  2. Step 2: you log in on Google’s own page, on Google’s own domain, and approve the list of scopes. The site is not involved and cannot see any of it.
  3. Step 3: Google redirects your browser back to the site with a short-lived authorization code in the URL.
  4. Step 4: the site’s server exchanges that code, over a direct back-channel call, for tokens. This step needs the client secret and the code verifier, which never touch the browser.
  5. Step 5: the site calls the API with the access token to fetch the data it was granted, and nothing more.
  6. Why the extra hop with a code, instead of just handing over a token? Because URLs leak. They land in browser history, server logs and referrer headers. A code is single-use and worthless without the secret.
  7. OpenID Connect is a thin layer on top. OAuth 2.0 alone answers “may this site read your calendar”. It does not answer “who are you”.
  8. OpenID Connect adds an ID token, a JWT about you specifically, plus a standard userinfo endpoint and the standard scopes openid, profile and email. That is the part that makes it a login system.

TECHNICAL35.12.5 the engineer’s version#

  1. bcrypt was published by Niels Provos and David Mazieres at USENIX in 1999, built on the Blowfish cipher (Bruce Schneier, 1993). Its cost parameter is log-2, so cost 12 means 2^12 key expansion rounds.
  2. bcrypt’s real limits: it truncates input at 72 bytes, and some implementations mishandle the NUL byte. Do not pre-hash into it without base64 encoding first, or you can shorten the effective input.
  3. scrypt came from Colin Percival in 2009 and is specified in RFC 7914 (2016). It added memory hardness, to blunt custom hardware.
  4. Argon2 won the Password Hashing Competition in July 2015. Authors: Alex Biryukov, Daniel Dinu and Dmitry Khovratovich. It is specified in RFC 9106 (September 2021). Use the Argon2id variant.
  5. Current OWASP Password Storage Cheat Sheet settings, as of 2026:
Algorithm Recommended setting
Argon2id m=19456 KiB, t=2, p=1
bcrypt cost factor 10 or more
scrypt N=2^17, r=8, p=1
PBKDF2-SHA256 600,000 iterations
  1. A pepper is a site-wide secret mixed in before or after hashing, kept outside the database, ideally in a key management service. It protects against a database-only leak. It is optional; salting is not.
  2. NIST SP 800-63B-4, “Digital Identity Guidelines: Authentication and Authenticator Management”, published 31 July 2025, is the current reference.
  3. It says: require at least 8 characters, accept at least 64, allow all printable ASCII and Unicode, and do not impose composition rules such as “one capital and one symbol”.
  4. It also says do not force periodic rotation without evidence of compromise, and do check new passwords against known-breached lists. Forced 90-day rotation is now explicitly discouraged, reversing decades of advice.
  5. JWT is RFC 7519 (May 2015), sitting on JWS (RFC 7515), JWE (RFC 7516), JWK (RFC 7517) and JWA (RFC 7518). Bearer usage is RFC 6750.
  6. The four classic JWT mistakes, all still seen in production:
Mistake Why it is fatal
Storing in localStorage any XSS reads it directly
No exp claim the token never dies
Accepting alg: none signature check skipped
Not pinning the algorithm RS256 forced down to HS256
  1. The alg: none and algorithm-confusion attacks were publicized by Tim McLean in March 2015. In the confusion attack the server is tricked into verifying an HMAC using the public RSA key as the shared secret, which the attacker also has. Always pin the expected algorithm on the verify side.
  2. localStorage versus cookies, stated plainly: localStorage is readable by any script running on the page, so one cross-site scripting hole exports every token. An HttpOnly cookie is not reachable from JavaScript at all.
  3. The cost of cookies is cross-site request forgery, which SameSite=Lax or Strict plus a CSRF token handles. Chrome made SameSite=Lax the default in version 80, February 2020.
  4. OAuth 2.0 is RFC 6749 (October 2012), replacing OAuth 1.0 (RFC 5849, 2010). PKCE, Proof Key for Code Exchange, is RFC 7636 (September 2015).
  5. RFC 9700, “Best Current Practice for OAuth 2.0 Security”, published 30 January 2025 as BCP 240, is now the document to follow. It requires PKCE for all clients including confidential ones, forbids the implicit grant and the resource owner password credentials grant, and requires exact string matching of redirect URIs.
  6. OAuth 2.1 is an IETF draft, not yet an RFC as of 2026. It folds RFC 9700’s advice into one document. Say “draft”, not “standard”.
  7. OpenID Connect Core 1.0 was published by the OpenID Foundation in February
    1. Discovery lives at /.well-known/openid-configuration; signing keys at a JWKS endpoint.
  8. Single sign-on in enterprises is still largely SAML 2.0, an OASIS standard from March 2005, using signed XML assertions over HTTP POST. New systems use OIDC; you will meet SAML because it is entrenched, not because it is better.
  9. Multi-factor authentication means two of: something you know, something you have, something you are. Two passwords are not two factors.
Factor Standard Real weakness
SMS code none, carrier route SIM swap, SS7
TOTP app RFC 6238, 2011 phishable in real time
Push approve vendor specific fatigue, blind taps
Passkey WebAuthn, FIDO2 device loss, recovery
  1. TOTP (RFC 6238, May 2011) builds on HOTP (RFC 4226, December 2005): a 30-second time step, 6 digits, HMAC-SHA1 by default over a shared secret.
  2. WebAuthn became a W3C Recommendation on 4 March 2019, with Level 2 in April
    1. Passkeys are discoverable WebAuthn credentials synced by the platform.
  3. Passkeys are phishing-resistant by construction: the credential is bound to the origin, so a look-alike domain simply gets no signature. That property is an established fact from the protocol, not a marketing claim.
  4. RBAC was formalized by David Ferraiolo and Richard Kuhn at NIST in 1992 and by Ravi Sandhu and colleagues in 1996, and standardized as INCITS 359-2004.
  5. ABAC, attribute-based access control (NIST SP 800-162, 2014), decides from attributes of subject, object, action and environment. It is more expressive and much harder to audit. Most teams want RBAC with a few ownership checks.
  6. Tools: bcrypt, argon2-cffi, libsodium; jwt.io and the jose libraries for inspecting tokens; openssl rand -base64 32 for secrets; Keycloak, Auth0, Okta and Entra ID as identity providers.

WORDS35.12.6 remember these#

  1. Authentication — proving who you are — verifying a claimed identity against one or more authenticators.
  2. Authorization — deciding what you may do — evaluating a policy over subject, action and resource after identity is established.
  3. Salt — a per-user random extra — a unique non-secret value that defeats precomputed rainbow tables.
  4. Pepper — a site-wide secret extra — a key held outside the database and mixed into the hash input or output.
  5. Work factor — how slow the hash is on purpose — the cost, memory and parallelism parameters tuned to a target verify time.
  6. Session — a ticket the server remembers — server-side state keyed by an opaque high-entropy identifier held in a cookie.
  7. JWT — a signed pass the user carries — a JSON Web Token, base64url header, payload and signature under RFC 7519.
  8. Access token — the short-lived key to the API — a bearer credential with a minutes-long exp claim.
  9. Refresh token — the long-lived key to get new keys — a revocable credential exchanged at the token endpoint, usually rotated on each use.
  10. OAuth 2.0 — letting a site act for you without your password — a delegated authorization framework, RFC 6749, with PKCE per RFC 9700.
  11. OpenID Connect — the login layer on top of OAuth — an identity layer adding an ID token and a userinfo endpoint.
  12. RBAC — permissions attached to job titles — role-based access control per INCITS 359-2004, users to roles to permissions.

35.13 Application architecture#

PLAIN35.13.1 in simple words#

  1. Architecture is the set of decisions that are expensive to change later. It is not about which framework you pick.
  2. A monolith is one program that does everything. One codebase, one build, one deploy, usually one database.
  3. Microservices means the same work split into many small programs that talk over the network, each with its own deploy and often its own database.
  4. The honest advice, given by most experienced engineers, is this: start with a monolith. Almost every team should.
  5. Inside any of them, the code is arranged in layers, so that the part drawing the screen is not also the part talking to the database.
  6. Some answers are expensive to compute and rarely change, so we keep a copy ready to hand. That copy is a cache.
  7. Some work does not need to happen while the user waits. We drop a note into a queue and a separate worker picks it up later.
  8. Sometimes the server needs to tell the browser something without being asked, which normal request-and-reply cannot do.
  9. And because anyone can call your server as fast as they like, you have to cap how often each caller may do it. That is rate limiting.

PLAIN35.13.2 a picture in your head#

  1. Think of a restaurant.
  2. A monolith is one kitchen. Everybody cooks in the same room, shares the same fridge, and shouts across the pass when something is ready.
  3. Communication is instant and free. Nobody has to book a phone call to ask whether the sauce is done.
  4. When the room gets too crowded, you cannot make just the grill section bigger. You have to build a second identical kitchen.
  5. Microservices are several small kitchens in separate buildings: one does bread, one does sauces, one does desserts.
  6. Each can be scaled, replaced or rebuilt without closing the others. But every request between them is now a phone call that can be busy, slow or dropped.
  7. The caching layer is the pass with finished dishes under a heat lamp. Serving from there takes seconds instead of minutes.
  8. The queue is the ticket rail. Orders go up, cooks take them down in order, and nothing is lost if one cook steps away.

Where this comparison breaks:

  1. A kitchen phone call fails loudly and immediately. A network call can succeed at one end and be lost at the other, so the sender never learns which.
  2. Real kitchens do not silently duplicate an order because the first acknowledgement went missing. Distributed systems do exactly that, which is why every message consumer must be safe to run twice.

PLAIN35.13.3 a worked example#

  1. A product page that shows a price, a stock count and a review average.
  2. Version one asks the database on every single request. At 500 requests a second, that is 1,500 queries a second for data that changes hourly.
  3. Version two caches the assembled result in Redis for 60 seconds.
def product(pid):
    key = f"product:{pid}"
    hit = redis.get(key)
    if hit:
        return json.loads(hit)          # about 0.5 ms
    data = build_from_db(pid)           # about 40 ms
    redis.setex(key, 60, json.dumps(data))
    return data
  1. With a 95 percent hit rate the average response time falls from about 40 ms to about 2.5 ms, and the database sees 75 queries a second instead of 1,500.
Layer Typical hit time Who it serves
CDN edge 5 to 30 ms everyone, worldwide
HTTP browser cache 0 ms one returning user
Redis 0.3 to 1 ms all app servers
In-process memory 0.0001 ms one server only
PostgreSQL 1 to 50 ms the source of truth
  1. Now the queue. When an order is placed we must charge the card, send an email, generate a PDF invoice and update a search index.
  2. Doing all four inline makes the user wait about 3 seconds and couples the checkout button to the email provider being awake.
  3. Doing only the charge inline, and pushing the other three onto a queue, gives a 200 ms response and a checkout that survives the email provider going down.
@app.post("/orders")
def create_order(req):
    order = save_order(req)     # inline, must be certain
    charge_card(order)          # inline, user needs the answer
    queue.enqueue("send_receipt", order.id)
    queue.enqueue("build_invoice", order.id)
    queue.enqueue("index_order", order.id)
    return {"id": order.id}     # about 200 ms
  1. The rule that makes this safe: the worker must be idempotent, meaning running it twice has the same effect as running it once.
  2. So send_receipt records that it sent receipt number 5512 before it exits, and checks that record on entry. Otherwise a retry mails the customer twice.

PLAIN35.13.4 what is really happening inside#

  1. Layers, from the outside in. The transport layer receives HTTP and knows nothing about your business. The service or application layer holds the rules. The data access layer talks to the database. The domain holds the concepts.
  2. The discipline is that dependencies point one way, inward. The database layer must never import the HTTP layer.
  3. MVC is the oldest version of this idea. The Model holds data and rules, the View renders, the Controller receives input and coordinates.
  4. In a modern web app the View often lives in the browser and the Controller is an HTTP route handler, which is why the term drifted.
  5. Caching happens at four separate places and they are independent: in the browser, at a CDN, in a shared store like Redis, and inside the process.
  6. HTTP caching is negotiated with headers. Cache-Control: max-age=3600 says how long a copy stays fresh. ETag gives a version fingerprint, so a stale copy can be revalidated with a cheap 304 Not Modified instead of a resend.
  7. A CDN is a network of servers placed near users. It caches your responses physically close to the reader, which cuts the speed-of-light cost that no amount of server tuning can remove.
  8. The hard part of caching is not storing. It is knowing when a stored answer became a lie. There are two strategies: expire on a timer, or actively delete the entry when the underlying data changes.
  9. Queues decouple in time. The producer writes a message and returns. The broker stores it durably. A consumer takes it, does the work, and acknowledges.
  10. If the consumer crashes before acknowledging, the message reappears. That is at-least-once delivery, and it is why duplicates happen.
  11. Failed messages retry with growing gaps, and after a limit are moved to a dead-letter queue so one poisoned message cannot block the line forever.
  12. Server-to-browser messaging has three shapes. Polling asks repeatedly. Server-sent events keeps one HTTP response open and streams text down it. WebSockets upgrades the connection to a two-way channel.
  13. Rate limiting works by counting requests per key, usually per API key or per IP address, and refusing once the count passes a threshold in a time window.

TECHNICAL35.13.5 the engineer’s version#

  1. Conway’s Law, from Melvin Conway’s 1967 paper “How Do Committees Invent?”, states that a system’s structure copies the communication structure of the organization that built it. Microservices without matching team boundaries produce a distributed monolith, which has every cost and no benefit.
  2. The term microservices was popularized by James Lewis and Martin Fowler in March 2014. Fowler’s own follow-up, “MonolithFirst” (June 2015), argues that teams who started monolithic and split later did better than those who began distributed.
  3. The honest trade-off table:
Concern Monolith Microservices
Local call cost nanoseconds milliseconds, can fail
Deploy one unit, all at once independent per service
Transaction one ACID commit sagas, eventual consistency
Debugging one stack trace distributed tracing needed
Scaling whole app together per service, precisely
Team size that fits 1 to 30 many teams, own on-call
  1. Amazon’s Prime Video team published in March 2023 that moving an audio-and-video monitoring service from distributed serverless components back into a single process cut infrastructure cost by over 90 percent. It is one case, not a law, but it punctured the idea that splitting always wins.
  2. A modular monolith is the current mainstream advice: one deployable unit with strictly enforced internal module boundaries, so a later split is mechanical rather than archaeological.
  3. MVC comes from Trygve Reenskaug at Xerox PARC in 1979, in Smalltalk-80. Related patterns: MVP, MVVM (Microsoft, 2005), and hexagonal architecture (Alistair Cockburn, 2005).
  4. HTTP caching is specified in RFC 9111 (June 2022), which replaced RFC 7234. Key directives: max-age, s-maxage for shared caches only, no-store, private, must-revalidate, and stale-while-revalidate from RFC 5861.
  5. Validators are ETag with If-None-Match, and Last-Modified with If-Modified-Since. A match returns 304 with no body.
  6. Cache patterns: cache-aside (read through the app, the code above), write-through, write-behind, and refresh-ahead. Cache-aside is the default because it is the only one that is simple to reason about.
  7. A cache stampede happens when a popular key expires and a thousand requests all miss and all rebuild it at once. Fixes: a mutex or single-flight lock, probabilistic early expiry, or stale-while-revalidate.
  8. Akamai, founded in 1998 out of MIT work by Tom Leighton and Daniel Lewin, created the commercial CDN. Cloudflare followed in 2009, Fastly in 2011.
  9. Message brokers: RabbitMQ (2007, implementing AMQP 0-9-1), Apache Kafka (built at LinkedIn by Jay Kreps, Neha Narkhede and Jun Rao, open sourced in 2011), Amazon SQS (generally available 2006), NATS, and Redis Streams.
  10. Kafka is a distributed append-only log with consumer offsets, so replay is normal. RabbitMQ is a broker that deletes on acknowledgement. They solve different problems and are frequently confused.
  11. Job frameworks by language: Celery and RQ for Python, Sidekiq for Ruby, BullMQ for Node, Quartz and Spring Batch for Java, Hangfire for .NET.
  12. Retry policy in practice: exponential backoff with full jitter, a maximum attempt count, and a dead-letter queue. Without jitter, retries synchronize and produce a thundering herd against a recovering service.
  13. The real-time comparison:
Property Polling SSE WebSocket
Direction client asks server to client both ways
Protocol plain HTTP plain HTTP upgrade, RFC 6455
Reconnect trivial automatic you write it
Binary data yes no, UTF-8 text yes
Proxy trouble none rare occasional
Good for slow updates feeds, tokens, logs chat, games, cursors
  1. WebSocket is RFC 6455 (December 2011); it starts as an HTTP request with Upgrade: websocket and then leaves HTTP framing entirely. RFC 8441 (2018) added bootstrapping over HTTP/2.
  2. Server-sent events are part of the HTML Living Standard, exposed as the EventSource API, with Content-Type: text/event-stream. The browser reconnects on its own and resumes with Last-Event-ID.
  3. SSE is the quiet default for AI token streaming, dashboards and progress bars, because it is ordinary HTTP and needs no special infrastructure.
  4. Rate limiting algorithms and what they actually do:
Algorithm Behaviour
Fixed window simple, allows 2x burst at edge
Sliding log exact, memory grows with traffic
Sliding counter near-exact, cheap, common
Token bucket allows a controlled burst
Leaky bucket smooths output to a fixed rate
  1. The response is 429 Too Many Requests, defined in RFC 6585 (April 2012), with a Retry-After header from RFC 9110. The RateLimit-Limit and RateLimit-Remaining headers are widely used by convention but are still an IETF Internet-Draft, not an RFC, as of 2026.
  2. Tools: redis-cli --stat and INFO stats for hit ratio, curl -I to read cache headers, nginx limit_req and Envoy for edge limiting, OpenTelemetry with Jaeger or Tempo for distributed tracing.

WORDS35.13.6 remember these#

  1. Monolith — one program that does it all — a single deployable unit sharing one process, build and usually one datastore.
  2. Microservice — a small program with its own deploy — an independently deployable service owning its data behind a network API.
  3. Modular monolith — one deploy, firm inner walls — enforced module boundaries inside a single artifact, splittable later.
  4. Cache — a ready-made copy of a slow answer — a store trading staleness for latency, governed by a TTL or explicit invalidation.
  5. CDN — servers near the user — a geographically distributed edge cache for static and cacheable dynamic responses.
  6. ETag — a version fingerprint for a response — a validator enabling conditional requests and 304 Not Modified replies.
  7. Queue — a durable to-do list between programs — a broker giving at-least-once delivery with acknowledgement and retry.
  8. Idempotent — safe to run twice — an operation whose repeated application gives the same result as one application.
  9. Dead-letter queue — the box for messages that keep failing — a sink for messages exceeding the retry limit, kept for inspection.
  10. WebSocket — a two-way phone line — a full-duplex framed protocol over one TCP connection, RFC 6455.
  11. Server-sent events — a one-way news feed — a long-lived HTTP response streaming text/event-stream with automatic reconnection.
  12. Rate limit — a cap on how often you may ask — a per-key request budget over a window, refused with HTTP 429.

35.14 Mobile apps#

PLAIN35.14.1 in simple words#

  1. A phone app can be built in three broadly different ways, and the choice decides most of what follows.
  2. Native means writing in the language the phone maker intends, with the phone maker’s own toolkit. Two apps, two codebases, two teams’ worth of work.
  3. Cross-platform means writing once in a shared language and letting a framework produce something that runs on both phones.
  4. Web-based means it is really a website, wrapped so it can be installed and opened from the home screen. That is a progressive web app, a PWA.
  5. On iPhone, native means Swift, usually with the SwiftUI toolkit. On Android, native means Kotlin, usually with Jetpack Compose.
  6. The main cross-platform choices are React Native, which uses JavaScript and the real platform controls, and Flutter, which uses Dart and paints every pixel itself.
  7. There is one more thing that has no equivalent on the web: a stranger at Apple or Google reads your app and decides whether it may exist.
  8. On the web you deploy and it is live. On a phone store you submit, you wait, and you can be told no.
  9. What you actually upload is a file. On Android it is an APK or an AAB. On iPhone it is an IPA. Both are, underneath, ordinary ZIP archives.

PLAIN35.14.2 a picture in your head#

  1. Think about writing a book that must be published in two countries with different languages and different censors.
  2. Native is hiring two native writers, each producing a book that reads perfectly naturally in its own country. Best result, double the cost, and the two books drift apart over time.
  3. Cross-platform is writing once and translating. React Native is a good human translator using each country’s own idioms and typesetting. Flutter is printing your own book, with your own fonts and paper, and shipping it unchanged to both countries.
  4. Flutter’s version always looks identical everywhere, which is a feature if you want your brand, and a problem if readers expected the local style.
  5. A PWA is publishing on a website that both countries can already read. No censor, no printing, instant updates, and no place on the bookshop shelf.

Where this comparison breaks:

  1. A translated book cannot suddenly need a printing press the translator does not own. A cross-platform app frequently needs a device feature the framework has not wrapped yet, and then you write native code anyway.
  2. And a censor reads a book once. App review happens on every single update, so it is a recurring tax on shipping, not a one-time gate.

PLAIN35.14.3 a worked example#

  1. The same screen, a counter with a button, written four ways. Notice how similar the modern declarative shapes have become.
// iOS native: Swift with SwiftUI
struct CounterView: View {
  @State private var n = 0
  var body: some View {
    VStack {
      Text("Count: \(n)")
      Button("Add") { n += 1 }
    }
  }
}
// Android native: Kotlin with Jetpack Compose
@Composable
fun Counter() {
  var n by remember { mutableStateOf(0) }
  Column {
    Text("Count: $n")
    Button(onClick = { n++ }) { Text("Add") }
  }
}
// Flutter: Dart
class Counter extends StatefulWidget { ... }
// inside State:
Column(children: [
  Text('Count: $n'),
  ElevatedButton(
    onPressed: () => setState(() => n++),
    child: const Text('Add')),
])
// React Native: JavaScript
function Counter() {
  const [n, setN] = useState(0);
  return (<View>
    <Text>Count: {n}</Text>
    <Button title="Add" onPress={() => setN(n + 1)} />
  </View>);
}
  1. Now the honest comparison for a real product decision.
Approach Performance Device access
Native Swift or Kotlin best available everything, day one
Flutter very close to native good, plugin gaps
React Native good, JS bridge cost good, plugin gaps
PWA fine for content most limited
  1. And the effort side, which is what actually decides most projects.
Approach Effort for both phones Team you need
Native roughly 180 percent iOS plus Android devs
Flutter roughly 110 percent Dart devs, some native
React Native roughly 110 percent web devs, some native
PWA roughly 60 percent your existing web team
  1. The “180 percent” is not 200 because design, backend and product work are shared. The extra 10 percent on cross-platform is the native code you end up writing anyway for permissions, push and store plumbing.
  2. A concrete rule that holds up: if the app is mostly forms, lists and content, any of these works and you should pick by team. If it is a camera, a game, a map with heavy custom rendering, or something that must feel invisible, go native.

PLAIN35.14.4 what is really happening inside#

  1. Native code is compiled ahead of time to machine code for the device’s processor and calls the operating system’s own UI objects directly. There is no layer in between, so there is no layer to be slow.
  2. React Native runs your JavaScript in a JavaScript engine on the phone, and that JavaScript creates and updates real platform views. A Text really is a UILabel on iOS and a TextView on Android.
  3. So React Native inherits the platform’s look and accessibility for free, and pays a cost whenever data crosses between JavaScript and the native side.
  4. Flutter does the opposite. It ships its own rendering engine and draws every button, scrollbar and cursor itself onto a blank canvas.
  5. That gives identical output on both platforms and total design control, and means Flutter must re-implement anything the platform changes, including accessibility behaviour and text selection.
  6. A PWA is a website with three additions: a manifest file naming the app and its icons, a service worker script that can serve files while offline, and a requirement to be served over HTTPS.
  7. The service worker is the key piece. It is a script that sits between the page and the network, can answer requests from a local cache, and keeps running briefly after the page closes.
  8. Store review, step by step. You build a signed package, upload it, fill in metadata and screenshots, and it enters a queue.
  9. Automated scanning runs first: malware checks, private API usage, permission declarations, size limits. Then, on Apple’s store, a human opens the app.
  10. The human checks the guidelines: does it do something useful, does it match its description, does it collect data it declared, does it route payments for digital goods through the store’s own system.
  11. Rejection is normal and is not the end. You get a message, you fix it, you resubmit, and the clock restarts.
  12. What this means for shipping is the real point: you cannot fix a live bug in ten minutes. Plan for a review delay on every release, keep a server-side switch to turn broken features off without a new build, and never ship on a Friday hoping to patch on Saturday.
  13. Now the files themselves, which ties directly to the file-format material in Chapter 20: a file’s identity comes from the bytes at its start, not from its extension.
  14. Open an APK in a hex viewer and the first four bytes are 50 4B 03 04, which is PK followed by two control bytes. That is the ZIP local file header signature, named after Phil Katz.
  15. An APK is a ZIP. Rename it to .zip, unzip it, and you can read the whole structure. So is an IPA. So is a .jar, a .docx and an .epub.
  16. Inside an APK you find compiled code, compiled resources, a compiled manifest and a signature folder. Inside an IPA you find a folder called Payload holding one .app bundle.
app.apk (ZIP)               app.ipa (ZIP)
+- AndroidManifest.xml      +- Payload/
|    (binary XML, not text) |   +- MyApp.app/
+- classes.dex              |       +- MyApp  (Mach-O)
+- resources.arsc           |       +- Info.plist (binary)
+- res/                     |       +- Assets.car
+- lib/arm64-v8a/*.so       |       +- _CodeSignature/
+- assets/                  |       +- embedded.mobileprovision
+- META-INF/  (signatures)  +- iTunesMetadata.plist
  1. The manifest inside an APK is not readable text. It is Android binary XML, a compact encoding with a string pool, which is why opening it in a text editor shows mush. The tool aapt2 dump badging prints it properly.
  2. classes.dex holds Dalvik Executable bytecode: all your Kotlin and Java classes merged into one register-based bytecode file, not the stack-based .class files the Java compiler first produced.
  3. Info.plist inside an iOS app is a property list, usually stored in Apple’s binary plist format starting with the bytes bplist00, not XML.

TECHNICAL35.14.5 the engineer’s version#

  1. Swift was announced at Apple’s WWDC in June 2014, designed by a team led by Chris Lattner, and open sourced in December 2015. Swift 6.3 is the current release in 2026, shipping with Xcode 26.
  2. SwiftUI was announced at WWDC in June 2019. UIKit, from iPhone OS 2.0 in 2008, remains fully supported and is still required for some controls, so real apps mix both through UIViewRepresentable.
  3. Kotlin came from JetBrains, announced in 2011, version 1.0 in February 2016. Google announced first-class Android support in May 2017 and a Kotlin-first policy in May 2019.
  4. Jetpack Compose reached 1.0 in July 2021. The older approach, XML layouts with findViewById or view binding, is still everywhere in existing code.
  5. React Native was open sourced by Facebook in March 2015. Its New Architecture, with the Fabric renderer and TurboModules replacing the old asynchronous bridge, became the default in version 0.76 (October 2024). Version 0.87 was released on 10 August 2026.
  6. Flutter’s first stable release, 1.0, was December 2018, after a 2017 alpha. Flutter 3.47 is current in 2026. It uses Dart, compiled ahead of time to native code for release builds, and the Impeller renderer, which replaced Skia as the default on iOS and on Android API 29 and above.
  7. Other real options: Kotlin Multiplatform, stable since November 2023, which shares business logic while leaving the UI native; .NET MAUI (May 2022); Capacitor with Ionic; and Tauri for desktop.
  8. Progressive web app is not a standard; it is a name for a set of standards used together: the Web App Manifest (a W3C specification), Service Workers (W3C), and a secure context requirement.
  9. The term was coined by Frances Berriman and Alex Russell in 2015. Safari on iOS 16.4, released 27 March 2023, finally added Web Push and badging for home-screen web apps, closing the largest gap.
  10. Remaining PWA gaps on iOS as of 2026: no Web Bluetooth, no Web NFC, limited background execution, storage that can be evicted, and no place in the App Store search results, which is a distribution problem, not a technical one.
  11. App Store review facts. The App Store opened on 10 July 2008. Apple states that on average 90 percent of submissions are reviewed in less than 24 hours. That is an average, and complex or first-time submissions take longer.
  12. Common rejection causes are guideline 2.1 (crashes and incomplete information), 4.2 (minimum functionality, which kills thin website wrappers), 3.1.1 (digital goods must use in-app purchase), and privacy label mismatches.
  13. Commission is 30 percent, or 15 percent under the App Store Small Business Program for developers below one million US dollars a year, and 15 percent on subscriptions after a subscriber’s first year.
  14. The EU Digital Markets Act forced alternative app marketplaces and third-party browser engines on iOS in the EU from iOS 17.4, March 2024. This is jurisdiction-specific and still changing; check the current position rather than trusting any book, including this one.
  15. Google Play review facts. Review is mostly automated with human escalation. New personal developer accounts must run a closed test with at least 12 testers for 14 continuous days before applying for production access, a rule introduced in November 2023 and later relaxed from 20 testers to 12.
  16. Google Play has required the Android App Bundle for new apps since August
    1. Play requires apps to target API level 36, Android 16, by 31 August 2026.
  17. An AAB is a publishing format, not an installable one. Play uses it to generate split APKs per device: one base, plus configuration splits for the device’s screen density, CPU architecture and language. Typical download size saving is 15 to 20 percent.
  18. APK signing schemes, and which Android version introduced each:
Scheme Since What it covers
v1 (JAR) Android 1.0 each file, per entry
v2 Android 7.0, 2016 whole APK bytes
v3 Android 9, 2018 adds key rotation
v3.1 Android 13, 2022 rotation targeting
v4 Android 11, 2020 streaming install
  1. iOS code signing uses a certificate plus a provisioning profile embedded as embedded.mobileprovision. _CodeSignature/CodeResources holds a hash of every file in the bundle, so changing one byte after signing breaks launch.
  2. App thinning means the store delivers only the slices a device needs. Apple calls the pieces app slicing, bitcode (deprecated with Xcode 14, 2022) and on-demand resources.
  3. Inspection commands worth knowing:
unzip -l app.apk            # it really is a ZIP
aapt2 dump badging app.apk  # readable manifest
apksigner verify -v app.apk # which schemes signed it
unzip -p app.ipa 'Payload/*.app/Info.plist' | plutil -p -
codesign -dvvv MyApp.app    # signing identity
  1. Where experts disagree: whether Flutter’s own-rendering approach is a strength or a liability. One camp values pixel-identical output and one codebase. The other argues that redrawing platform widgets means permanently chasing platform changes, especially in accessibility and text input. Both positions have shipped very large apps.

WORDS35.14.6 remember these#

  1. Native app — built with the phone maker’s own tools — compiled against the platform SDK, using the platform’s UI framework directly.
  2. Cross-platform — one codebase, both phones — a shared runtime or compiler targeting iOS and Android from common source.
  3. PWA — a website you can install — a manifest plus a service worker over HTTPS, installable to the home screen.
  4. Service worker — a script that answers requests offline — a background worker intercepting fetches and managing a cache.
  5. APK — the Android app file — a ZIP containing DEX bytecode, binary XML manifest, resources.arsc and signature blocks.
  6. AAB — what you upload, not what installs — the Android App Bundle, from which Play generates per-device split APKs.
  7. IPA — the iPhone app file — a ZIP containing a Payload folder holding one signed .app bundle with a Mach-O binary.
  8. DEX — Android’s compiled code format — Dalvik Executable, register-based bytecode ahead-of-time compiled by ART at install.
  9. App review — a person deciding if your app may ship — guideline conformance checking applied to every submission and update.
  10. Code signing — a seal proving nobody changed the app — certificate-backed hashes over every file, verified at install and launch.

35.15 Testing, performance and accessibility for the web#

PLAIN35.15.1 in simple words#

  1. Three things separate a demo from a product: it keeps working when you change it, it is fast on a normal phone, and everyone can use it.
  2. Tests are code that checks your other code. You run them automatically, every time, so a mistake is caught in seconds rather than by a customer.
  3. There are three sizes of test. Small ones check one function. Medium ones check that two real parts fit together. Large ones drive a real browser like a real person.
  4. Small tests are fast and cheap, so write many. Large tests are slow and fragile, so write few, and only for the paths that must never break.
  5. Speed is not a feeling, it is a measurement, and Google publishes three specific numbers called the Core Web Vitals.
  6. One measures how long until the main thing appears. One measures how long the page takes to answer a tap. One measures how much the page jumps around while loading.
  7. Lighthouse is a free tool built into Chrome that scores a page and tells you what to fix.
  8. Accessibility means the site works for people who cannot see it, cannot use a mouse, or need much larger text.
  9. That is not a nice extra. It is a legal requirement in many places, and the basics are genuinely small: correct HTML tags, text for images, working keyboard navigation, readable contrast, a visible focus outline, and labels on inputs.

PLAIN35.15.2 a picture in your head#

  1. Think of building a bridge and checking it three ways.
  2. You test each bolt in a jig before it goes in. That is a unit test: thousands of them, seconds each, and a failure tells you exactly which bolt.
  3. You test one assembled span under load. That is an integration test: fewer, slower, and a failure tells you the span is wrong but not which bolt.
  4. You drive a lorry across the finished bridge. That is an end-to-end test: a handful, minutes each, and a failure tells you only that something is wrong.
  5. All three are worth doing. Only doing the lorry is how you end up rebuilding the bridge to find one bad bolt.

Where this comparison breaks:

  1. A bridge bolt does not change while you are testing it. Software does. Tests also protect against your future self, which a bolt jig never has to do.
  2. And a lorry crossing is reliable. Browser tests fail randomly for reasons that have nothing to do with your code, which is called flakiness, and a test suite nobody trusts is worse than no suite at all.

PLAIN35.15.3 a worked example#

  1. A single button component, tested at all three levels.
// unit: does the function do arithmetic
test("adds tax", () => {
  expect(withTax(100, 0.2)).toBe(120);
});

// integration: do component and state work together
test("shows total after adding", async () => {
  render(<Cart />);
  await user.click(screen.getByRole("button",
                   { name: /add/i }));
  expect(screen.getByText("120")).toBeVisible();
});
// end-to-end: a real browser, a real server
test("checkout works", async ({ page }) => {
  await page.goto("/product/42");
  await page.getByRole("button", { name: "Buy" }).click();
  await expect(page.getByText("Order placed")).toBeVisible();
});
  1. Notice the queries use getByRole and the accessible name. A test written that way fails if the button stops being reachable by a screen reader, so the test suite quietly enforces accessibility.
  2. Now the performance numbers. Here is one real page before and after work.
Metric Before After Good if
LCP 4.8 s 1.9 s 2.5 s or less
INP 420 ms 140 ms 200 ms or less
CLS 0.31 0.02 0.1 or less
Lighthouse 38 94 90 or more
  1. What actually produced those changes, in order of effect.
  2. LCP: the hero image was 2.4 MB PNG. Converting to a 180 KB WebP, adding width and height, and marking it fetchpriority="high" removed most of the delay.
  3. CLS: the same missing width and height were letting the text jump when the image finally arrived. Reserving the space fixed it. A web font swapping in late caused the rest, fixed with font-display: optional.
  4. INP: a 300 ms sorting loop ran inside the click handler. Breaking it up and deferring the non-urgent part let the browser paint first.
  5. The general lesson: two of the three fixes were one HTML attribute pair. Most real performance problems are not clever, they are neglected basics.

PLAIN35.15.4 what is really happening inside#

  1. A test runner imports your code, runs functions, compares results, and reports the ones that differ. There is no magic in it.
  2. An end-to-end runner starts a real browser process and drives it through an automation protocol, then reads back the rendered page.
  3. Test doubles replace slow or unreliable parts. A mock stands in for a thing and records how it was called. A fake is a working cheap version, such as an in-memory database.
  4. The trap is mocking so much that the test only proves your mocks agree with each other. Prefer a real database in a container over a mocked one.
  5. LCP, Largest Contentful Paint, is the time from navigation start until the biggest image or text block in the visible area finishes rendering. The browser keeps updating its guess and stops at the first user interaction.
  6. INP, Interaction to Next Paint, watches every click, tap and key press for the whole visit, measures from the input to the next frame painted, and reports roughly the worst one.
  7. CLS, Cumulative Layout Shift, adds up how much visible content moved without the user causing it. Each shift scores the fraction of the screen affected times the distance moved. It has no unit.
  8. There are two kinds of measurement and they answer different questions. Lab data comes from one run on one machine with simulated conditions. Field data comes from millions of real visits on real phones.
  9. Lighthouse gives lab data. It is repeatable, so it is good for finding causes and for stopping regressions in a build pipeline.
  10. Core Web Vitals as Google actually uses them are field data, collected from real Chrome users who opted in, and judged at the 75th percentile.
  11. That percentile choice matters: it means three quarters of your visits must be good. Your own fast laptop on office fibre tells you nothing.
  12. Accessibility works through the accessibility tree, a second structure the browser builds beside the DOM, where each element has a role, a name, a state and a value.
  13. A screen reader reads that tree, not your pixels. A <button> arrives with role “button” and is focusable and clickable by keyboard, all for free.
  14. A <div onclick=...> styled to look like a button arrives with no role, no name, no keyboard focus and no Enter key handling. That is why the tag you choose is the single biggest accessibility decision you make.

TECHNICAL35.15.5 the engineer’s version#

  1. The testing pyramid was described by Mike Cohn in his 2009 book “Succeeding with Agile”. A common rule of thumb is roughly 70 percent unit, 20 percent integration, 10 percent end-to-end, though the exact split is convention, not a finding.
  2. Kent C. Dodds’ “testing trophy” (2018) is the main counter-argument: it puts the most weight on integration tests, on the grounds that they catch the most real bugs per minute spent. Experts genuinely disagree here; both shapes ship good software.
Level Tools Typical time
Unit Vitest, Jest, pytest, JUnit 5 1 to 50 ms
Integration Testing Library, supertest 50 to 500 ms
Container-backed Testcontainers, MSW 0.5 to 5 s
End-to-end Playwright, Cypress, Selenium 5 to 60 s
  1. Dates: Selenium began in 2004 with Jason Huggins at ThoughtWorks; WebDriver became a W3C Recommendation in June 2018. Cypress launched in 2017. Playwright came from Microsoft in 2020, written by former Puppeteer authors. Jest came from Facebook in 2014; Vitest arrived in 2021 alongside Vite.
  2. Coverage percentages measure lines executed, not behaviour verified. A suite at 100 percent coverage with no assertions proves nothing. Use coverage to find untested files, never as a target to hit.
  3. Core Web Vitals thresholds, current as of 2026. A page passes only if all three are in the good band at the 75th percentile, split by mobile and desktop.
Metric Good Needs work Poor
LCP 2.5 s or less 2.5 to 4.0 s over 4.0 s
INP 200 ms or less 200 to 500 ms over 500 ms
CLS 0.1 or less 0.1 to 0.25 over 0.25
  1. History: LCP and CLS became Core Web Vitals in May 2020. INP was announced as a replacement for First Input Delay in May 2023 and became a Core Web Vital on 12 March 2024. FID was retired because it measured only the delay before the first handler ran, not how long the page took to respond.
  2. Field data comes from the Chrome User Experience Report, CrUX, which publishes monthly aggregates. PageSpeed Insights shows CrUX and Lighthouse side by side, and they routinely disagree, which confuses people. They are measuring different populations.
  3. Lighthouse performance score weights, current version:
Metric Weight
Total Blocking Time 30 percent
Largest Contentful Paint 25 percent
Cumulative Layout Shift 25 percent
First Contentful Paint 10 percent
Speed Index 10 percent
  1. Score bands are 0 to 49 poor, 50 to 89 needs improvement, 90 to 100 good. Lighthouse applies simulated mobile throttling by default, which is why a local score is lower than what your machine feels like.
  2. Note that INP is not in the Lighthouse score. Lighthouse uses Total Blocking Time as its lab proxy, because a single automated load performs no interactions. Fixing TBT usually fixes INP, but not always.
  3. Other measurement tools: WebPageTest for filmstrips and multi-location runs, the Chrome DevTools Performance panel for flame charts, and the web-vitals JavaScript library to report real user metrics to your own backend.
  4. Accessibility is governed by WCAG, the Web Content Accessibility Guidelines. WCAG 2.0 became a W3C Recommendation in December 2008, 2.1 in June 2018, and WCAG 2.2 on 5 October 2023. WCAG 3.0 remains a working draft.
  5. Conformance levels are A, AA and AAA. AA is the level almost all law and procurement references. AAA is not expected for whole sites.
  6. The genuinely non-negotiable basics, each tied to its success criterion:
Basic Criterion Concrete rule
Semantic HTML 1.3.1, 4.1.2 real button, nav, h1 to h6
Alt text 1.1.1 describe it, or empty alt
Keyboard 2.1.1, 2.1.2 everything reachable, no traps
Contrast 1.4.3, 1.4.11 4.5:1 text, 3:1 large and UI
Focus visible 2.4.7, 2.4.11 a clear ring, never obscured
Labels 3.3.2, 1.3.5 every input has a <label for>
  1. Contrast detail: 4.5 to 1 for normal text, 3 to 1 for large text, meaning at least 18 point or 14 point bold, which is about 24 CSS pixels or 18.66 pixels bold. AAA raises those to 7 to 1 and 4.5 to 1.
  2. New in WCAG 2.2 and often missed: 2.4.11 Focus Not Obscured, 2.5.8 Target Size Minimum at 24 by 24 CSS pixels, and 3.3.8 Accessible Authentication, which forbids requiring a cognitive test such as transcribing a code by hand with no paste allowed.
  3. The first rule of ARIA, from the W3C ARIA Authoring Practices Guide: do not use ARIA if a native HTML element with the needed semantics exists. Incorrect ARIA is measurably worse than none, because it overrides what the browser already knew.
  4. Never remove a focus outline without replacing it. outline: none with no substitute is the single most common accessibility regression in modern CSS.
  5. Automated tools find only part of the problem. Deque, the maker of axe, reports that automated testing catches roughly a third to a half of issues depending on the ruleset; the figure varies by study, so treat it as approximate. Nothing replaces a keyboard-only pass and a screen reader pass.
  6. Law, briefly and with dates, because it drives budgets: Section 508 in the United States was refreshed in January 2017 to reference WCAG 2.0 AA. The European Accessibility Act’s requirements applied from 28 June 2025. The United States Department of Justice ADA Title II rule of April 2024 requires WCAG 2.1 AA for state and local government sites, phasing in from 2026.
  7. Tools: axe DevTools and @axe-core/playwright for automated checks, Lighthouse’s accessibility category, WAVE, and the real screen readers: NVDA and JAWS on Windows, VoiceOver on macOS and iOS, TalkBack on Android.

WORDS35.15.6 remember these#

  1. Unit test — checks one small piece alone — an isolated assertion over a single function or module with no external dependencies.
  2. Integration test — checks two real parts together — a test exercising collaborating components, often with a real database in a container.
  3. End-to-end test — drives the app like a person — a full-stack test through a real browser via an automation protocol.
  4. Flaky test — passes and fails without changes — a non-deterministic test, usually a timing or ordering race, that destroys trust in the suite.
  5. LCP — how long until the main thing appears — Largest Contentful Paint, good at 2.5 seconds or less at the 75th percentile.
  6. INP — how long the page takes to answer a tap — Interaction to Next Paint, good at 200 milliseconds or less.
  7. CLS — how much the page jumps about — Cumulative Layout Shift, unitless, good at 0.1 or less.
  8. Lab data — one measured run — a synthetic test under fixed throttled conditions, repeatable and good for regression gates.
  9. Field data — what real users got — real user monitoring aggregated at a percentile, such as the Chrome User Experience Report.
  10. Accessibility tree — what a screen reader reads — a parallel tree of roles, names, states and values derived from the DOM.
  11. WCAG AA — the level the law usually means — the middle conformance level of the Web Content Accessibility Guidelines, currently 2.2.
  12. Semantic HTML — using the tag that means the thing — elements chosen for meaning, giving roles and keyboard behaviour without extra code.

35.16 A realistic learning path#

PLAIN35.16.1 in simple words#

  1. You have almost certainly seen a picture called a 180-day web development roadmap. It usually says something close to this.
Days Topic
1 to 25 HTML and CSS
26 to 55 JavaScript
55 to 60 responsive design
61 to 75 React
76 to 105 Node and MongoDB
106 to 125 GitHub and APIs
126 to 180 cloud and projects
  1. We are going to take that roadmap seriously, because it is not stupid. Parts of it are genuinely right, and it has helped a lot of people start.
  2. What it gets right: the big order. Structure first, then styling, then the language, then a framework, then a server. You cannot begin at React.
  3. What it also gets right: giving JavaScript more time than HTML and CSS. That ratio is correct, and most beginners get it backwards.
  4. And it ends with projects and putting things online, which is exactly where a learning path should end.
  5. Now the three things it gets wrong, said kindly, because each one costs beginners real months.
  6. First, it teaches MongoDB before SQL, and in fact never teaches SQL at all. That is the wrong way round.
  7. Second, it puts GitHub on day 106. Version control belongs on day one, before your first file, not after your first framework.
  8. Third, and biggest: days are not the unit. “25 days of HTML” measures how long you sat there. It does not measure whether you can build a page.
  9. The honest replacement is a list of stages, each defined by a thing you have built and can show, and each finished when the thing works.
  10. Some people reach stage four in two months. Some take eight. Both are normal and neither number tells you anything about how good they will be.

PLAIN35.16.2 a picture in your head#

  1. Think about learning to cook rather than learning to watch cooking.
  2. A day-count roadmap is a timetable that says “week three: sauces”. You attend week three. At the end of it you have watched sauces being made.
  3. A stage-based path says “you are finished with sauces when you have served four different sauces to someone who ate them and told you the truth”.
  4. The second one cannot be faked. There is no way to have served the sauce without having made the sauce.
  5. It also self-corrects. If your sauce splits, you go back and learn about emulsions because you need to, and that lesson sticks permanently, in a way no scheduled lecture on emulsions ever does.
  6. And notice what the timetable never mentions: cleaning as you go, knife safety, tasting. Those are not week seven. They are every single day.
  7. Version control, testing and accessibility are the cleaning as you go.

Where this comparison breaks:

  1. Cooking gives you an answer in twenty minutes. Software gives you a bug that takes two days, and during those two days it feels like you are learning nothing. You are learning the most.
  2. Also, a chef can taste the dish. You cannot tell whether your code is good by looking at it. That is what other people’s review and real users are for.

PLAIN35.16.3 a worked example#

  1. Here is the replacement. Eight stages. Each one names what to learn, what to build, and the specific signal that you are finished with it.
Stage Learn Build Done when
0 Tools git, shell, editor a repo of notes you can undo a commit
1 Pages HTML, CSS, layout 3 real pages it works at 320px
2 Language JavaScript, DOM a to-do, no library you explain the loop
3 Data SQL, schema design a 4-table schema you write a join
4 Server HTTP, one backend a JSON API + login passwords are hashed
5 Client React or Svelte rebuild stage 2 you know what it saved
6 Ship deploy, CI, logs put it online a stranger used it
7 Depth tests, a11y, speed improve stage 6 Lighthouse over 90
  1. Stage 0, tools, roughly one week. Learn git init, add, commit, push, branch, merge, and how to read git log. Learn cd, ls, grep, and how to kill a stuck process.
  2. Build: a repository where you keep your notes from this book, committed daily. The point is not the notes. The point is that committing becomes automatic before you have anything valuable to lose.
  3. Done when: you can deliberately make a mess, and get out of it with git restore, git reset or git revert, without asking anyone.
  4. Stage 1, pages, roughly three to five weeks. HTML semantics, the box model, flexbox, grid, and media queries. Responsive is not a separate topic; it is how you write CSS from the first line.
  5. Build three pages that are not tutorials: a page about something you actually care about, a copy of a real site’s layout, and a form-heavy page.
  6. Done when: you resize the browser to 320 pixels wide and nothing overlaps, nothing overflows, and you did not need to look up flexbox.
  7. Stage 2, the language, roughly six to eight weeks, and this is the stage people rush. Variables, types, functions, arrays, objects, closures, this, promises, async/await, modules, and the DOM API.
  8. Build a to-do application with no framework at all. Then a small game. Then something that calls a public API and handles the error case properly.
  9. Done when: you can predict the output order of a piece of asynchronous code on paper before running it, and you are right.
  10. Stage 3, data, roughly three weeks, and this is the stage the popular roadmap skips entirely. Learn SQL before you learn any other database.
  11. Build a schema for something real with four or five related tables: users, posts, comments, tags, and a join table. Load it with a few thousand rows.
  12. Done when: you can write a query with two joins, a GROUP BY and a HAVING clause, and explain why you added an index.
  13. Stage 4, the server, roughly four to six weeks. Pick one backend and stay there. Node with Express or Fastify, or Python with FastAPI or Django.
  14. Build a JSON API with real registration and login, sessions or tokens done properly, input validation, and correct status codes.
  15. Done when: passwords are stored with bcrypt or Argon2id, a wrong password gives 401, a valid user hitting somebody else’s record gives 403, and you can say why those two are different.
  16. Stage 5, the client framework, roughly four weeks. Now, and only now, learn React, Svelte or Vue.
  17. Build: rebuild your stage 2 to-do app in the framework, against your stage 4 API. Same app, second time.
  18. Done when: you can name three specific things the framework did for you that you had written by hand, and one thing it made harder.
  19. Stage 6, shipping, roughly two weeks. A domain, HTTPS, environment variables, a build pipeline, a real deploy, and logs you can read at 2 a.m.
  20. Done when: someone you have never met has used the thing and told you something about it. That single event teaches more than the previous month.
  21. Stage 7, depth, ongoing forever. Tests, accessibility, performance, security and reading other people’s code.
  22. Done when: nothing. This stage does not end, and that is the job.

PLAIN35.16.4 what is really happening inside#

  1. Why SQL before MongoDB, concretely. A relational schema forces you to decide, on day one, what your data actually is and how the pieces relate.
  2. That decision is the hard part of software, and doing it early with a database that argues back is how you learn it.
  3. A document database lets you postpone every one of those decisions. That feels faster and it genuinely is faster, for about six weeks.
  4. Then you need to know which users commented on which posts, and you discover you now have to write the join yourself, in application code, without transactions, at three in the morning.
  5. There is nothing wrong with MongoDB. It is a good database for documents that really are documents. It is a bad first database because it does not teach modelling, and modelling is the transferable skill.
  6. Also, in almost every job you will apply for, the database is PostgreSQL, MySQL or SQL Server. SQL is asked about in interviews. Mongo query syntax almost never is.
  7. Why git on day one. Every hour you spend without version control is an hour where the worst outcome is losing everything and the second worst is being afraid to try things.
  8. Version control is not an advanced topic. It is the thing that makes learning safe, because you can break anything and get back.
  9. It is also, bluntly, the one skill every single job requires. Learning it on day 106 means 105 days of work you cannot show anyone.
  10. And be precise about the words: git is the version control program, made by Linus Torvalds in 2005. GitHub is one website that hosts git repositories, founded in 2008 and bought by Microsoft in 2018. Learn git. GitHub takes an afternoon once you know git.
  11. Why days are the wrong unit. Two people both “do 25 days of HTML”. One watches videos for two hours a day. One builds and breaks pages for four. They are not in the same place, and the calendar cannot tell them apart.
  12. Worse, a day count creates a false finish line. Day 26 arrives, you move to JavaScript because the plan says so, and you carry a shaky understanding of the box model into everything that follows.
  13. A build target has no such problem. The page either survives 320 pixels or it does not. You cannot argue with it and you cannot schedule past it.
  14. What the popular roadmap leaves out entirely, and should not: testing, accessibility, security, debugging, and reading code you did not write.
  15. Those are not advanced. Debugging in particular is most of the job, and no roadmap ever allocates a single day to it.
  16. One more honest thing. You will feel, somewhere around stage 2, that everyone else understands this and you do not. That feeling is not evidence. It is what learning something genuinely hard feels like from the inside, and it arrives for everyone, including the people you are comparing yourself to.

TECHNICAL35.16.5 the engineer’s version#

  1. Realistic hour costs, from teaching experience rather than from a course listing. Treat these as approximate and personal.
Stage Focused hours At 15 h/week
0 Tools 10 to 20 about 1 week
1 Pages 60 to 100 4 to 7 weeks
2 Language 120 to 200 8 to 13 weeks
3 Data 40 to 70 3 to 5 weeks
4 Server 80 to 120 5 to 8 weeks
5 Client 50 to 80 3 to 5 weeks
6 Ship 20 to 40 1 to 3 weeks
  1. That totals roughly 380 to 630 focused hours to be employable at a junior level, which at a genuine 15 hours a week is about 26 to 42 weeks.
  2. “180 days” at 3 hours a day is 540 hours, so the popular roadmap’s total is not unreasonable. Its problem is the distribution and the ordering, not the size.
  3. The distribution the roadmap gets wrong, in numbers: it gives 25 days to HTML and CSS and 15 days to React, while giving zero to SQL, zero to testing and zero to debugging.
  4. A portfolio that actually gets interviews contains three items, not fifteen:
Project What it must prove
A full-stack app auth, a real schema, deploy
A focused tool you can finish something small
A contribution you can read others’ code
  1. Concrete quality bar for the full-stack app: a live URL, a README with setup steps that work on a clean machine, at least a handful of tests, migrations rather than a hand-edited database, and no secrets in the repository.
  2. Reading the git history of a candidate’s project tells an interviewer more than the project does. Many small commits with real messages beat one commit called “initial commit” containing 40,000 lines.
  3. Order of technologies, with the reasoning stated as a rule: learn the thing that is underneath before the thing that abstracts it, unless the underneath thing is genuinely obsolete.
  4. So: HTML and CSS before Tailwind. JavaScript before React. SQL before an ORM. HTTP before a framework’s router. Git before any hosting website.
  5. The exception is real. Nobody should learn assembly before Python, or Objective-C before Swift. The rule applies one layer down, not all the way.
  6. TypeScript timing: learn it after stage 2, not during. Types over a language you cannot yet read are noise. Once you can read JavaScript, TypeScript takes about a week and pays for itself immediately.
  7. On tutorials, stated plainly: following a tutorial produces the feeling of competence without the substance, because every decision was already made for you. The cure is to build the same thing again, from an empty folder, with the tutorial closed.
  8. On AI assistants, separating fact from claim. Established fact: they generate working code for common tasks quickly and are excellent at explaining unfamiliar code. Active research: how much they change long-term skill acquisition in beginners; the studies are early and mixed. Marketing claim: that they remove the need to understand the fundamentals. If you cannot read the answer, you cannot tell when it is wrong, and it is confidently wrong often enough to matter.
  9. A defensible use during learning: write it yourself first, then ask for a critique. That order keeps the skill and gains the speed.
  10. What does not decide your career: which framework you chose. React, Vue, Svelte and Angular all teach the same underlying ideas of state, components and reconciliation, and moving between them takes a fortnight.
  11. What does decide it: whether you can take a vague problem, model the data, build something that works, and explain the trade-offs you made.

WORDS35.16.6 remember these#

  1. Roadmap — a picture of what to learn next — an ordered curriculum, useful as a sequence and misleading as a schedule.
  2. Stage gate — a built thing that proves readiness — an objective completion criterion replacing elapsed time as the unit of progress.
  3. Tutorial trap — following along and learning nothing — passive completion without independent recall or decision-making.
  4. Portfolio — the few things you can show — a small set of deployed, documented, version-controlled projects with real commit history.
  5. git — the version control program — a distributed content-addressable version control system, Linus Torvalds, 2005.
  6. GitHub — one website that stores git projects — a hosting and collaboration platform for git repositories, founded 2008.
  7. Full stack — you can do both halves — competence across the browser client, the server application and its datastore.
  8. Junior-ready — good enough to be paid to learn — able to ship a reviewed feature end to end with guidance, not to work unsupervised.

35.98 Common wrong ideas#

  1. Wrong: HTML is a programming language and knowing it means you can program. Right: HTML is a markup language. It describes structure and has no variables, no conditions and no loops. JavaScript is the only one of the three web languages that can compute anything.
  2. Wrong: !important fixes a specificity problem. Right: it hides one. It wins by leaving the normal cascade entirely, so the next person needs a second !important to override it, and after four rounds nobody can predict which rule applies. The real fix is a lower-specificity selector.
  3. Wrong: async and await make JavaScript run on several threads. Right: your JavaScript still runs on exactly one thread. await yields the thread while waiting, so other queued work can run. Parallelism needs Web Workers.
  4. Wrong: the virtual DOM is faster than touching the DOM. Right: a hand-written direct DOM update is always faster than a diff plus the same update. The virtual DOM buys predictable performance from declarative code, not raw speed, and its own authors have said so.
  5. Wrong: you need a framework to build a website. Right: a framework earns its place when many pieces of shared state must stay in sync. A content site, a form, a landing page and a blog are all better plain, and ship faster.
  6. Wrong: MongoDB should be your first database because it scales. Right: PostgreSQL handles the workload of almost every application ever written, has transactions and joins, and has a jsonb column type when you genuinely need flexible documents. Learn relational modelling first; it transfers everywhere.
  7. Wrong: hashing a password with SHA-256 is secure because SHA-256 is a strong hash. Right: SHA-256 is strong and fast, and fast is exactly the wrong property. A GPU tries billions of SHA-256 guesses a second. Use bcrypt, scrypt or Argon2id with a per-user salt, tuned to take about 100 milliseconds.
  8. Wrong: JSON Web Tokens are more secure than sessions. Right: they are a different trade, not a better one. They remove the server lookup and, with it, the ability to revoke. Sessions in an HttpOnly cookie are the safer default, and a JWT in localStorage is stolen by the first cross-site scripting bug.
  9. Wrong: microservices scale better, so start with them. Right: they let teams deploy independently, which is an organizational benefit with a technical cost. Every in-process call becomes a network call that can fail, and one ACID transaction becomes a saga. Start with a modular monolith and split when a specific team or scaling boundary forces it.
  10. Wrong: accessibility is a polish step near launch, and mostly means adding ARIA attributes. Right: it is decided by the tags you choose on the first day. A real <button> arrives with a role, keyboard focus and Enter handling; a styled <div> arrives with none. The W3C’s own first rule of ARIA is not to use ARIA when a native element already does the job.

35.99 Chapter summary in 20 lines#

  1. A web application runs in two places: a frontend on the user’s device that you do not control, and a backend on a server that you do. Never trust the first.
  2. HTML gives structure, CSS gives presentation, JavaScript gives behaviour, and only JavaScript is a programming language.
  3. Semantic HTML tags are not decoration. They set the role, keyboard behaviour and screen-reader name of everything on the page, for free.
  4. CSS resolves conflicts by origin, then specificity counted as inline, id, class and element, then source order. The box model, flexbox and grid replace guessing with rules.
  5. JavaScript has one thread, a call stack, a task queue and a microtask queue. Microtasks drain completely before the next task, which is why promise callbacks beat setTimeout(0) every time.
  6. The browser parses HTML into the DOM and CSS into the CSSOM, combines them into a render tree, then runs layout, paint and composite. Changing geometry forces a reflow; changing transform and opacity does not.
  7. The browser is a platform in its own right: storage, workers, fetch, canvas, WebGL and WebAssembly, all inside a same-origin sandbox that CORS relaxes deliberately and explicitly.
  8. A frontend framework’s real product is keeping the screen in agreement with the state. Virtual DOM diffing, fine-grained signals and compilation are three answers to that one problem.
  9. Build tooling exists because browsers want a few small optimized files while humans want many readable ones: bundling, transpiling, minifying, tree shaking and content-hashed names for caching.
  10. A backend program listens on a port, parses HTTP, routes the request through middleware to a handler, talks to a database, and serializes a reply. Every framework in every language is that same loop.
  11. Relational databases store facts once, in typed tables, joined on keys, with B+ tree indexes turning scans into lookups and ACID transactions making partial failure impossible.
  12. Choose a datastore by workload, not by fashion. PostgreSQL is the correct default; Redis, Elasticsearch, Cassandra and Neo4j each answer a specific question that PostgreSQL answers less well.
  13. An ORM removes repetitive code and adds the N+1 query trap. Use it for the ordinary ninety percent, read the SQL it generates, and write SQL yourself when the query is the point.
  14. Store passwords with a deliberately slow, salted hash: Argon2id or bcrypt, never a bare fast hash, and never the password itself.
  15. Sessions keep state on the server and can be revoked instantly. Tokens keep state with the client and scale without a lookup. Pick the trade knowingly, and keep credentials in HttpOnly cookies.
  16. OAuth 2.0 delegates authorization through an authorization code exchanged on a back channel with PKCE; OpenID Connect adds the ID token that turns it into a login system. Authentication is who you are; authorization is what you may do, and RBAC keeps the second one auditable.
  17. Architecture is caching at the right layer, moving slow work to a queue, choosing polling, server-sent events or WebSockets on merit, rate limiting every public endpoint, and starting with a monolith you could split later.
  18. Mobile means choosing between native Swift or Kotlin, cross-platform React Native or Flutter, and an installable web app, then living with a store review on every release. An APK and an IPA are both ZIP archives, which is Chapter 20’s lesson that the bytes, not the extension, define a file.
  19. Quality is measurable: a pyramid of fast unit tests under fewer browser tests, LCP at 2.5 seconds, INP at 200 milliseconds, CLS at 0.1 measured at the 75th percentile of real users, and WCAG 2.2 level AA as the floor rather than the ambition.
  20. Learn in stages defined by what you have built, not in days spent: tools, then pages, then the language, then SQL, then a server, then a framework, then shipping, then depth forever.