B4 · Part 1 · Foundations
The content model
Four types, two special cases, and one record that turns into several routes.
06On this page
The shape
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.
| Type | Carries | Decides |
|---|---|---|
| Topic | id, name, mark, accent, tagline, status, notes, blurb, levels, curriculumNotes, planned | The shelf, the site menu, the topic colour, and the topic's URL |
| Level | id, name, mark, tagline, blurb, bullets, checkpoint, syllabus | The reading path and the chapter grouping |
| Chapter | id, num, title, short, subtitle, levels, practice, ready, body | A page, a search entry, a sitemap row, a reading time |
| Exercise | id, chapter, level, title, brief, starter, hints, solution, tests, kind | A 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.
/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.
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.
Finished reading?
0 of 26 chapters read in this series