How this is builtGroundworkHow this is built
4 / 26 written chapters

B4 · Part 1 · Foundations

The content model

Four types, two special cases, and one record that turns into several routes.

6 min read2 diagrams6 sections
06On this page
  1. The shape
  2. The four types
  3. Two things that do not fit, and how they are made to
  4. One record, several routes
  5. The flag that does the most work
  6. One interface in the middle

The shape

Topic 21 of them Level 3 per topic Chapter 253 written Exercise 955 with tests 1..3 syllabus practice[] /react /level/react /react/<chapter> /problems/<id> Every arrow above is also a URL. The model is the routing.
A chapter does not own its exercises; it lists their ids in practice. That indirection is what lets the problems page be one flat list grouped by chapter, and what lets the mock interview draw coding questions from the same set without knowing about chapters at all.

The four types

All of them are declared in content/types.ts, which is short enough to read in a minute and is the most important file in the directory.

TypeCarriesDecides
Topicid, name, mark, accent, tagline, status, notes, blurb, levels, curriculumNotes, plannedThe shelf, the site menu, the topic colour, and the topic's URL
Levelid, name, mark, tagline, blurb, bullets, checkpoint, syllabusThe reading path and the chapter grouping
Chapterid, num, title, short, subtitle, levels, practice, ready, bodyA page, a search entry, a sitemap row, a reading time
Exerciseid, chapter, level, title, brief, starter, hints, solution, tests, kindA problem page, a playground session, a pass or fail, and possibly a mock question

A few fields do more than their names suggest. Topic.notes holds a file name such as "react.html", and notesHref() in lib/topics.ts strips the extension to make the URL. JavaScript's is "notes.html", which is why JavaScript lives at /notes rather than /js. Chapter.levels places a chapter on a level's reading path. Exercise.kind is either "function", the default, or "component": 439 exercises are functions graded by assertions, and 99 are React components rendered in a sandboxed iframe. The mock interview uses the same field to split its coding round from its machine-coding round.

Reading time is not stored anywhere. lib/content.ts drops the code, scripts, styles and diagrams from a body (everything inside <pre>, <script>, <style> and <svg>), strips the remaining tags, counts the words, divides by 180 and rounds, with a floor of two minutes — for a written chapter, so a code-heavy chapter reads as long as its prose, not as long as its listings. readTime() and chapterMetas() both check ready first and return zero for an outline chapter instead of flooring its empty body at two minutes. Every chapter and Git section follows that same rule. The interview book's round pages do not: lib/interviewBook.ts counts the words of each round's own fields at 220 a minute, with a floor of one minute.

Two things that do not fit, and how they are made to

The interview book is written in its own shape. content/interview-data.ts holds 27 rounds, each with a code, a title, metadata rows, company tiers, and a list of questions carrying an answer, what the interviewer is really testing, what to say, the trap, and the follow-ups. lib/interviewContent.ts converts each round into an ordinary Chapter by rendering those fields to HTML, so the reader, search and the sitemap never learn that it is different. The mock interview reads the raw rounds instead, because it needs the trap and the follow-ups as separate fields to build a rubric. Two sentences quote the size of the DSA track as placeholders in double braces, dsa.chapters and dsa.exercises, because this file may not import lib/content. lib/interviewCounts.ts fills them on the server from the written chapters and the exercises, and tests/claims.test.ts fails if a number or a placeholder is left behind.

The Git guide is one HTML string in content/git-body.ts with 16 sections. It has no levels and no chapters, gets a single page at /git, and is counted as 16 written chapters by lib/topicStats.ts so the totals on the home page include it.

One record, several routes

The model's best trick is that most of what the site serves is derived. Nobody writes a test case file or a mock question bank. They are computed from records that already exist, at build time.

Exercise solution + tests Interview round questions + traps /problems/<id> /problems/<id>/cases /mock/bank/coding /interview/<round> /mock/bank/<stage> 538 pages 222 translatable 439 + 99 machine 27 chapters 10 talk stages
Green boxes are written by hand; everything on the right is generated during the build. The /cases file for a problem that cannot be translated still exists; it just says why.

The test-case files are the clever one. lib/polyglot/record.ts takes an exercise's JavaScript solution, wraps its function in a spy, runs each test body in Node's vm module during the build, and records every call's arguments and what the reference solution returned. It then infers a typed signature from those values: int, float, bool, string, or lists of them. Those become language-neutral cases that a Python, Ruby, PHP, Lua, C or C++ solution can be graded against in the browser. It refuses, and says why, for React components, for exercises about JavaScript's async model, and for tests that are not a plain call compared to a value. Of the 538 exercises, 222 come out translatable, carrying 2,048 recorded cases between them.

The flag that does the most work

A chapter carries ready: boolean. That single field is the difference between a written chapter and an outline, and it decides several things at once. An outline still gets a page, because every chapter id is returned from generateStaticParams, but the page shows a roadmap card — the syllabus section's items, plus a "Meanwhile" link to a related written topic — instead of a body, is marked noindex, has no Listen button and no "mark as read", is left out of the sitemap and the cross-topic search index, and counts no reading time. The syllabus marks it with an open circle instead of a tick. It is why the site can show 611 chapters while honestly claiming 253.

One interface in the middle

Pages do not import content files. They go through lib/content.ts: chapters(), chapterMetas(), exercise(), exercisesForLevel(), readTime(). chapterMetas() exists so that lists can carry every chapter without its body, which is what keeps the chapter list of a chapter page from shipping the whole topic. That is what makes adding a chapter a one-file job: drop the file in, add it to the topic's barrel and its level's syllabus, and the route, the search entry, the sitemap row, the reading time and the progress tracking all appear without being told to.

Why TypeScript files instead of a database

Three things a database would take away. The chapter shape is checked by the compiler, so a malformed chapter fails the build instead of rendering oddly in production. Content, rendering and styling change in one commit and revert in one command. And the whole corpus is covered by the same tooling as the code: spell-checking, and fifteen tests in tests/content.test.ts that walk every chapter and exercise looking for unbalanced <pre> and <code> tags, diagrams without an aria-label, missing subtitles, exercises without tests, and syllabus sections that point at chapters that do not exist. The derived routes above are the fourth reason: a build step can compute from content it can import, and a database would have made each of them a query.