πŸ“š Sanjay's Study Notes Home Roadmap HTML CSS JavaScript TypeScript Node SQL & Prisma React Tailwind Auth Apps Git & Deploy

03 Β· JavaScript β€” Getting Started

HTML is structure, CSS is looks β€” JavaScript is behavior. It's a real programming language: it can store values, make decisions, react to clicks, and change the page live.

Where these names come from (learn the why, remember forever)

1. The Console β€” your playground

Every browser has a live JavaScript prompt. Open your page, press F12 (or right-click β†’ Inspect), and click the Console tab. Type code, press Enter, see the result instantly β€” no files needed to experiment.

Reading the console Red text = an error. Grey/black text = normal output or a return value. A grey undefined after a line is not a failure β€” it's just what that line handed back. Don't panic at grey.

2. Variables β€” labeled boxes

A variable is a named box that holds a value. let makes a new one; = stores a value in it.

let bookTitle = "The Alchemist";
let copies = 4;

console.log(bookTitle);  // The Alchemist
console.log(copies);     // 4
"The Alchemist" bookTitle ← the name (label) ← the value (contents)
The name labels the box; the value is what's inside. console.log(x) prints the contents.
"already been declared" let can't create the same name twice in one session. Re-running let bookTitle = ... throws a red error. Fix: refresh the page (F5) to reset, then run it once.

3. Two errors you already met

ErrorMeansUsual cause
ReferenceError: x is not definedThere's no box called x at allTypo, or used before you made it
value is undefined (grey, no error)The box exists but is emptylet x; with nothing stored
"not defined" β‰  "undefined" They sound identical but are different problems. No box (ReferenceError) vs empty box (undefined). Test it: let p; console.log(p) β†’ grey undefined. console.log(q) (never declared) β†’ red ReferenceError.
First debugging question When you see a red error, first ask: "is this even about the code I just wrote?" The file: "unique security origin" warning, for example, is about how the page was opened β€” nothing to do with your typed code. Half of errors aren't what you think.

4. Data types

Every value has a type, and the type decides how it behaves. Check any value with typeof.

TypeWhatExamples
Stringtext, always in quotes"The Alchemist"
Numbernumbers, no quotes4, 19.99
Booleanjust two valuestrue, false

The quotes are the whole difference: "4" is text, 4 is a number. They look the same but behave differently.

5. The + trap (very important)

The + sign does two different jobs depending on the types:

number + number 5 + 3 = 8 (math) string + number "5" + 3 = "53" (glue)
If either side is a string, + glues (concatenates). Otherwise it does math.
The bug this causes β€” you'll hit it soon Everything typed into an HTML form comes out as a string. So reading the Age field gives "4", not 4:
let age = "4";        // from a form β†’ TEXT
console.log(age + 1);  // want 5, get "41" 😱
The fix (coming later): Number(age) converts text β†’ number, so Number(age) + 1 β†’ 5.

6. Type conversion β€” fixing the + trap

You can convert a value from one type to another. Note the capital letter β€” capitalization matters in JavaScript.

CommandDoesExample
Number(x)text β†’ numberNumber("4") β†’ 4
String(x)number β†’ textString(4) β†’ "4"
let age = "4";         // text (like a form gives you)
console.log(age + 1);          // "41"  ← the bug
console.log(Number(age) + 1);  // 5     ← convert first, then math
NaN β€” "Not a Number" Number("hello") β†’ NaN. It means "I tried to make a number and failed." Three things to know: In the Library: an empty or non-numeric Age field makes Number(...) return NaN β€” check for it before trusting the value.

7. Functions β€” write once, reuse forever

A function is a reusable recipe. Write it once, then call it as many times as you like with different inputs.

function greet(name) { return "Hello " + name; } ← name (to call it) ← parameter (input) ← output (handed back)
Defining β‰  running. You must call it: greet("Sanjay") β†’ "Hello Sanjay".
function lateFine(days) {   // β‚Ή2 per day late
    return days * 2;
}

lateFine(5);              // 10
let fine = lateFine(5);   // store it β†’ fine is 10, ready to use
The big one: return vs console.log
console.log(x)return x
DoesPrints for a human to seeHands the value back to your code
Can you reuse the value?No β€” it just appeared on screenYes β€” store it, add it, display it
A function with no return hands back undefined:
function lateFine2(days) { console.log(days * 2); }
let f = lateFine2(5);   // prints 10, but f is undefined!
Rule: want to use the result later β†’ return it. Just want to peek β†’ console.log it.

8. Making decisions β€” if / else

Decisions start with a comparison, which asks a yes/no question and returns a boolean (true/false).

OperatorAsksExample
> <greater / less than5 > 3 β†’ true
>= <=greater/less than or equal4 >= 4 β†’ true
===exactly equal? (three =)4 === 4 β†’ true
!==not equal?4 !== 5 β†’ true
= vs === One = stores a value (let x = 4). Three === compares (x === 4). Mixing them up is a classic bug. Always use === (not ==) β€” reason comes later.

An if uses a comparison to choose which block runs:

function bookStatus(copies) {
    if (copies > 0) {
        return "Available";
    } else {
        return "Out of stock";
    }
}
bookStatus(4);  // "Available"
bookStatus(0);  // "Out of stock"
Always check the boundary The single most bug-prone spot is the edge value. Here, copies > 0 is correct but copies >= 0 would be a bug: 0 >= 0 is true, so it would say "Available" with zero copies on the shelf. When you write a comparison, always ask: "what happens right at the edge?" (at 0, at the due date exactly, at an empty string). One character (> vs >=) is the whole bug.

9. Arrays β€” a list in one variable

An array holds an ordered list. Square brackets, comma-separated items.

Why "array"? An array means an orderly arrangement β€” like soldiers "arrayed" in rows, or things laid out in fixed positions. That's exactly what it is: items in a fixed order, each with its own numbered slot (index). The word already tells you order matters.
let books = ["Harry Potter", "The Alchemist", "Atomic Habits"];
Harry Potter The Alchemist Atomic Habits [0] [1] [2] first item!
Counting starts at 0. First item = books[0], last = books[books.length - 1].
ActionCodeResult
Read first itembooks[0]"Harry Potter"
How many itemsbooks.length3
Last itembooks[books.length - 1]"Atomic Habits"
Add to the endbooks.push("Wings of Fire")list grows to 4
Out of bounds = undefined, not an error With 3 items, books[3] returns undefined β€” no crash, no warning. So a mysterious undefined often means "I read past the end of an array." JavaScript won't tell you; you have to notice.

10. Loops β€” do something to every item

A for loop walks the whole list automatically, however long it is.

for (let i = 0; i < books.length; i = i + 1) {
    console.log(books[i]);
}

Three parts in the ( ), separated by semicolons:

  1. let i = 0 β€” counter starts at 0 (first index)
  2. i < books.length β€” keep going while this is true
  3. i = i + 1 β€” add 1 after each pass
Off-by-one β€” use <, not <= With 3 items, valid indexes are 0, 1, 2. i < length stops at the right place. i <= length runs one time too many β†’ reads books[3] β†’ prints undefined at the end. The most famous loop bug there is.
Two-track counting: i vs i + 1 Use i to reach into the array (books[i], 0-based). Use i + 1 only to display a human number (1-based):
for (let i = 0; i < books.length; i = i + 1) {
    console.log((i + 1) + ". " + books[i]);  // 1. Harry Potter …
}

11. Objects β€” name your values, don't count them

An array is great at "many," but a single thing β€” a book β€” has several named parts: title, author, category, copies. An object stores them by name (key) instead of by position.

Array [ ] vs Object { } Array = a list, reached by position: book[3]. Object = named parts, reached by name: book.copies. Square brackets for order; curly braces for structure.
let book = {
    title: "Wings of Fire",
    author: "A.P.J. Abdul Kalam",
    category: "Autobiography",
    copies: 3
};

book.title;   // "Wings of Fire"  ← dot + name
book.copies;  // 3
The danger objects fix β€” position is fragile (you felt this one) Stored as an array, meaning is tied to position. Insert one value in the middle and everything after it shifts:
// author at [1], copies at [3]
let book = ["Wings of Fire", "Kalam", "Autobiography", 3];
book[3];  // 3 (copies) βœ…

// a teammate inserts the year in the middle…
let book = ["Wings of Fire", "Kalam", 1999, "Autobiography", 3];
book[3];  // "Autobiography" 😱 β€” copies silently moved to book[4]
No error. No crash. Every line trusting book[3] is now quietly wrong. With an object, book.copies keeps working no matter what else you add β€” because there's no position to shift. Meaning by name, not by position.
Missing key = undefined (same idea as out-of-bounds) book.publisher when there's no publisher key β†’ undefined, not an error. Ask an object for a name it doesn't have and it calmly shrugs β€” just like reading past the end of an array.
Reading and updating a field Read with the dot, and update with the dot + =. A book gets borrowed:
book.copies = book.copies - 1;  // read it, subtract, write it back β†’ 2
That single line is the seed of a real "Borrow" feature.

12. Array of objects β€” the shape of real apps

A library is many structured things, so you combine both tools: a list whose every slot holds an object. This is the single most common data shape on the web β€” it's what most APIs hand back.

let books = [
    { title: "Harry Potter",  author: "J.K. Rowling", category: "Fantasy", copies: 5 },
    { title: "Wings of Fire", author: "A.P.J. Abdul Kalam", category: "Autobiography", copies: 3 },
    { title: "The Alchemist", author: "Paulo Coelho", category: "Fiction", copies: 4 }
];

books.length;      // 3
books[0];          // the whole first object
books[1].author;   // "A.P.J. Abdul Kalam"  ← index first, THEN dot
Read it left to right books[1] hands you an object; then .author reaches inside that object. Index picks the book, dot picks the field. One step at a time.

13. The DOM β€” the page as objects you can change

When the browser loads your HTML, it builds a live tree of objects in memory β€” one object per element. That tree is the DOM (Document Object Model). Your <h1>, your <table> β€” each is now an object JavaScript can grab and change, and the browser instantly redraws.

It's the same objects you just learned A DOM element is an object with properties you read and write β€” exactly like book.copies. You already know how to work with objects; now you're working with the page as objects.
ToolDoes
documentthe whole page as an object β€” the entry point
document.getElementById("x")grab the one element whose id is "x"
.innerHTMLthe HTML inside an element β€” read it, or write it to change the page
document.getElementById("mainHeading").innerHTML = "Sanjay's Library";
// the <h1 id="mainHeading"> changes on screen instantly
Why id? A unique handle To grab one specific element out of hundreds, JS needs a unique name tag. That's the whole job of the id attribute β€” a unique handle so getElementById finds exactly one.
In-memory DOM vs. the file on disk (you nailed this) Change the page with JS, then press F5 β€” your change vanishes. Why? The edit lived only in the browser's in-memory DOM. The .html file on disk never changed, so refresh rebuilds the DOM fresh from the file. To make a change stick, the data it comes from has to live somewhere permanent β€” a file, a database. (Beginners constantly think refresh keeps DOM edits. It doesn't.)

14. Rendering a table from data β€” the payoff

Now snap it all together: array of objects (data) + for loop (visit each) + string building (make a <tr>) + innerHTML (inject). Delete the hand-typed rows β€” JavaScript generates them.

// empty container in the HTML:  <tbody id="bookRows"></tbody>

let rowsHTML = "";                       // start empty (accumulator)
for (let i = 0; i < books.length; i++) {
    rowsHTML = rowsHTML +                // append one <tr> per book
        "<tr><td>" + books[i].title +
        "</td><td>" + books[i].author +
        "</td><td>" + books[i].category +
        "</td><td>" + books[i].copies + "</td></tr>";
}
document.getElementById("bookRows").innerHTML = rowsHTML;
The big idea: UI is a function of your data Data now lives in one place (the array); the loop is written once and works for 3 books or 800. Change the data β†’ the UI regenerates. Add a book object β†’ a row appears, no HTML touched. This exact pattern β€” "the screen is a picture of the data" β€” is the core idea behind React, Vue, and every modern framework. You just did it by hand.
Why it beats hand-typed rows One source of truth. "Sort by author," "hide out-of-stock," "change all Fantasy copies" become a few lines over the array. Hand-typed, they'd be hundreds of manual edits. And the objects let you model real entities β€” a book is a thing with attributes, not loose values.
Two traps you met today

15. Events β€” making the page respond to the user

An event is something the user does β€” a click, a keypress. onclick says "when this is clicked, run this code." That's how a page stops being a poster and becomes an app.

<button onclick="borrowBook(0)">Borrow</button>
The one loop behind every interactive app User acts β†’ change the data β†’ re-render. A click runs borrowBook, which lowers copies in the array, then calls renderBooks() to redraw. The screen is always just a picture of the current data. Every button you ever wire up is this same loop.
Why bake the index into each button? When the render loop builds each row, it writes onclick='borrowBook(${i})' β€” so row 0's button becomes borrowBook(0), row 1's borrowBook(1), etc. Each button carries which book it controls, frozen in at render time. View the page source and you'll see the numbers sitting there.
Change data, THEN redraw β€” or nothing moves Updating books[i].copies changes the data, but the screen won't know until you call renderBooks() again. Forget the re-render and the number "won't change" even though the data did. Always: change β†’ re-render.

16. Reading form input β€” .value + Number()

To read what a user typed, grab the input and read its .value (for an <h1> you read .innerHTML; for an <input> you read .value).

let title  = document.getElementById("newTitle").value;
let copies = Number(document.getElementById("newCopies").value);  // convert!
books.push({ title: title, author: author, category: category, copies: copies });
"It worked without Number()" β€” the accident trap .value is always a string, even from type="number". Skipping Number() can seem fine β€” because -/*// force numbers ("3" - 1 = 2). But + is the trap: "3" + 1 = "31". So a sum of copies silently turns to garbage. "Works by accident" is the most dangerous bug. Convert input the moment it enters your program, and it behaves with every operator.

17. Template literals β€” readable strings with backticks

Backticks ` let you drop values straight into a string with ${ }, instead of gluing pieces with +. It reads like the final result, with fill-in blanks.

// old β€” bounce between quotes and +
"<td>" + book.title + "</td><td>" + book.copies + "</td>"

// template literal β€” reads like the output
`<td>${book.title}</td><td>${book.copies}</td>`
Three things to know
Watch stray spaces <td> ${book.title} </td> puts spaces around every cell's text. Harmless, but <td>${book.title}</td> is cleaner. Also: rowsHTML += x is shorthand for rowsHTML = rowsHTML + x.

18. forEach β€” let the array walk itself

A for loop makes you manage a counter (i = 0, i < length, i++) β€” pure bookkeeping, and where off-by-one bugs live. forEach does the walking; you just say what to do per item.

books.forEach(function (book, i) {
    // 'book' is the current object, 'i' is its index
    console.log(book.title);
});
A function handed to a function (a "callback") You give forEach a function, and it calls your function once per item, passing in the item (book) and its index (i). No counter, no books[i], no way to run one-too-many times β€” the whole off-by-one class of bug disappears. You describe what to do, not how to count (that's called declarative code).

19. localStorage β€” memory that survives refresh

Earlier you learned DOM changes vanish on refresh because they live only in memory. localStorage is a small box the browser keeps on disk, per site β€” write to it and it survives refresh, closing the tab, even a reboot.

// save (after every change):
localStorage.setItem("libraryBooks", JSON.stringify(books));

// load (on startup, BEFORE the first render):
let saved = localStorage.getItem("libraryBooks");
if (saved) { books = JSON.parse(saved); }
Why JSON.stringify / JSON.parse? localStorage only stores strings. Your books is an array-of-objects. JSON.stringify turns the whole structure into one text string (to save); JSON.parse turns it back into a real array (to load). JSON = JavaScript Object Notation β€” the standard text format for structured data (it's also what APIs send).
The if (saved) guard β€” the empty first run On the very first visit nothing is saved, so getItem returns null. JSON.parse(null) would blow up your app. So load only if something was saved; otherwise keep your default (seed) books. Boundary thinking again: handle the empty first case.
Seed data vs saved data Once saving works, your hardcoded books array is just seed data β€” used only on a fresh browser. After that, the load step overwrites it with saved data every visit. To reset, run localStorage.clear() in the console and the seed reappears.

20. Two bugs you solved yourself (worth remembering)

Declare inside vs outside the loop Same loop, two variables, opposite correct placement β€” decided by intent: Ask: remember across iterations, or start fresh? Remember β†’ outside. Fresh β†’ inside.
Leftover debug code sabotages features A localStorage.removeItem(...) left in the file ran on every load and wiped saved data β€” so persistence broke. The tell-tale "takes two refreshes" lag came from order: the code read storage at the top but deleted it at the bottom, so each load showed the previous load's data, then wiped it. Lesson: scratch/test code (a clear(), a console.log) belongs in the console, not committed in the file β€” clean it up.

21. Arrow functions β€” the modern short form

An arrow function is a shorter way to write a function. Same job, less typing β€” and it's what you'll see in every modern codebase, tutorial, and API doc.

// the long way you learned first:
books.forEach(function (book) {
    console.log(book.title);
});

// the arrow way β€” drop 'function', add => after the ( ):
books.forEach((book) => {
    console.log(book.title);
});
Why the arrow exists Callbacks (functions handed to functions) are everywhere in JS β€” forEach, filter, sort, event handlers. Writing function hundreds of times a day got old, so 2015's update added =>. Read it as "goes to": book goes to… console.log its title.
The one-liner shortcut (you did this) If the body is a single expression, drop the { } AND the return β€” the result returns automatically:
// long:
books.filter(function (b) { return b.copies <= 2; });

// one-liner β€” no braces, no return, same result:
books.filter((b) => b.copies <= 2);
Gotcha: if you keep the { }, you must keep return too. (b) => { b.copies <= 2 } returns undefined β€” braces mean "full body, return it yourself."

22. this β€” the hardest keyword, tamed

this means "the object I'm currently working for." The confusion: normal functions and arrow functions get their this from opposite places.

Normal functionArrow =>
Where this comes fromWhoever CALLS it β€” a fresh this per callHas NO this of its own β€” looks outward, like any variable
Mental model"Who's calling me right now?""What was this where I was written?"
let myBook = {
    title: "Harry Potter",
    normalShout: function () {
        setTimeout(function () {
            console.log(this.title);   // undefined 😱 β€” setTimeout calls it, not myBook
        }, 1000);
    },
    arrowShout: function () {
        setTimeout(() => {
            console.log(this.title);   // "Harry Potter" βœ… β€” arrow looks outward to arrowShout's this
        }, 1000);
    }
};
The misconception to kill (you held this one) "A normal function's this goes to its immediate parent" β€” NO. A normal function doesn't look outward at all. It gets a brand-new this from whoever calls it: myBook.normalShout() β†’ this = myBook; the same function called bare β†’ this = undefined/window. Only the arrow does the look-outward-through-scopes thing β€” exactly like looking up an ordinary variable.

23. Sorting β€” teach sort how to compare

books.sort(...) reorders the array, but it doesn't know what "smaller" means for your objects. You hand it a compare function that takes two items and answers with a number:

Your function returnssort concludes
negativea comes first
0leave them as-is
positiveb comes first
// numbers: subtraction gives exactly neg/0/pos β€” the classic trick
books.sort((a, b) => a.copies - b.copies);

// strings can't subtract β€” localeCompare gives the same neg/0/pos contract
books.sort((a, b) => a.title.localeCompare(b.title));
Your bug: sorted data, frozen screen You wrote the books.sort(...) line alone β€” and "nothing happened." console.log(books.map(b => b.copies)) proved the DATA had sorted; the SCREEN was still showing the old picture. Same lesson as the very first render: every function that touches books must end with saveBooks() β†’ renderBooks(books). That pair is the spine of the app.
Mutate vs copy β€” the map of your tools This is why the low-stock view needs no saveBooks(): filtering changes what you show, not what you have. And why indexOf(book) keeps Borrow/Delete correct after sorting β€” it finds the object by identity, not by old position.

24. Removing from an array β€” three tools

ToolDoesMutates?
books.splice(i, 1)removes 1 item at position i (your Delete button)YES
books.pop()removes the last itemYES
books.filter(...)doesn't remove β€” returns a new array without the unwanted itemsno
Why "splice"? From rope-work and film editing: to splice is to cut and rejoin. splice(i, 1) cuts one item out at position i and rejoins the ends β€” the array closes the gap (positions shift!). Rule of thumb: splice = surgery on the real array; filter = photocopy with items left out.

25. let vs var β€” why we never use var

Both declare variables. The difference is how far they leak: let lives only inside its { } block; var ignores blocks and leaks out to the whole function.

if (true) {
    var a = 1;
    let b = 2;
}
console.log(a);  // 1 β€” var LEAKED out of the block 😱
console.log(b);  // ReferenceError β€” let stayed inside βœ…
The history (why both exist) var is the 1995 original; its leaking caused a generation of silent bugs β€” remember your rowClass-outside-the-loop bug? var makes exactly that class of mistake invisible. let was added in 2015 to fix it. Modern rule: always let (or const), never var. You'll only meet var in old code β€” recognize it, don't write it.

26. map β€” transform every item (+ join)

forEach just visits each item. map visits AND transforms: it builds a new array where each item is whatever your function returned.

books.map((b) => b.title);
// ["Harry Potter", "Wings of Fire", "The Alchemist"]  ← new array, books untouched

books.map((b) => b.copies);
// [5, 3, 4]   ← handy debug trick: see one field of everything at a glance
Why "map"? From mathematics: a mapping pairs every input with an output. map does exactly that β€” same length, each item mapped through your function. Array in β†’ array out.
map + join β€” the modern renderBooks Your accumulator (rowsHTML += ...) works, but modern code says it in one thought: map each book to its <tr> string, then glue the pieces.
let rowsHTML = list
    .map((book) => `<tr><td>${book.title}</td>...</tr>`)   // array of row-strings
    .join("");                                             // one big string
Why join("")? map hands back an ARRAY of strings, but innerHTML needs ONE string. join glues the pieces using the separator you give it β€” and without "" the default separator is a comma, so you'd see stray commas between your table rows on screen.

27. Book ids β€” find things by identity, not position

Positions are fragile: sort the array and "book 3" is a different book. Real systems give every record a permanent, unique id and always look things up by it β€” that's exactly how databases work (you'll meet this again as a primary key).

{ id: 1, title: "Harry Potter", ... }

// buttons carry the id, not the row position:
`<button onclick='borrowBook(${book.id})'>Borrow</button>`

// find the OBJECT by id:
let book = books.find(b => b.id === id);

// find the POSITION by id (when splice needs one):
let i = books.findIndex(b => b.id === id);
books.splice(i, 1);
Generating the next id β€” and the sort trap "Last book's id + 1" breaks the moment you sort: the last row is no longer the newest book β†’ duplicate ids β†’ find grabs the wrong book (it stops at the first match). Safe way β€” highest id anywhere + 1:
let newId = Math.max(...books.map(b => b.id)) + 1;
Read inside-out: all ids β†’ biggest β†’ plus one. The ... is the spread operator: Math.max wants separate numbers, not an array β€” Math.max([1,2,3]) β†’ NaN, but Math.max(...[1,2,3]) β†’ 3 (the dots unpack the array into individual values).
Stale saved data (you hit this) You added ids to the seed array β€” but the page loads from localStorage, saved before ids existed. Result: every book.id β†’ undefined, and undefined + 1 β†’ NaN. One cause, two symptoms. Rule: when you change the data's shape, old saved data is now the wrong shape β€” clear it (localStorage.clear()) or convert it. Real apps call this a migration.
The = vs === bug β€” you wrote it live
books.find(b => b.id = id)    // 😱 ONE equals: ASSIGNS id to every book it visits
books.find(b => b.id === id)  // βœ… THREE: compares
The single = silently overwrote ids while searching β€” data corruption, no error, and find "succeeded" (assignment is truthy). Β§8 warned about this bug; now you've met it. In a condition, = is almost always a bug.

28. Reference vs copy β€” why find needs no "write back"

After let book = books.find(...), is book a copy you must save back into the array? No. It's the same object β€” book and the array slot both point at one object in memory.

let book = books.find(b => b.id === 1);
book === books[0];   // true β€” SAME object, two names

book.copies = 99;
books[0].copies;     // 99 β€” "the array changed" because there is only ONE object
The rule Objects and arrays are handled by reference β€” a variable holds a pointer to the object, not a duplicate. Only primitives (numbers, strings, booleans) get copied on assignment. So book.copies = book.copies - 1 already changed the book inside books β€” a second "write it back" line (books.find(...).copies = book.copies) just re-finds the same object and hands it a value it already has. Dead code.
This also explains two older mysteries

29. Dates β€” the last core tool (read-ahead)

Not built into the Library yet β€” read this so the concept isn't new when you meet it. Due dates, fines, "borrowed on" β€” all need the Date object.

let now = new Date();          // this exact moment
now.toISOString();               // "2026-08-25T14:30:00.000Z" β€” a SAVEABLE string

// date math goes through milliseconds:
let due = new Date();
due.setDate(due.getDate() + 14);         // 14 days from now β€” handles month-ends itself

// how late is a return? subtract β†’ milliseconds β†’ convert:
let msLate  = new Date() - due;               // negative = not late yet
let daysLate = Math.ceil(msLate / (1000*60*60*24));
Three things that surprise everyone

30. Linking data by id β€” how a real loan works (read-ahead)

Your ids unlock the pattern behind every real system: records that point at other records by id. A loan is not a copy of a book β€” it's a small object that references one:

let loans = [
    { loanId: 1, bookId: 3, member: "Sanjay", borrowedOn: "2026-08-25T…", returnedOn: null }
];

// "which book is loan 1 about?" β€” follow the id:
let loan = loans.find(l => l.loanId === 1);
let book = books.find(b => b.id === loan.bookId);   // same find you already use
Why point, not copy? If the loan copied the title and the book got renamed, the loan would silently disagree β€” two versions of the truth. Pointing by id keeps one source of truth (Β§14's big idea, now across arrays). returnedOn: null = "still out" β€” the guard-for-empty thinking again. This exact picture is what SQL databases call a foreign key β€” when you meet PostgreSQL in a later phase, you already know the concept.

← Prev: CSS Basics  Β·  Back to contents  Β·  Next: TypeScript β†’