Interactive course · about 6 hours

Writing an Application

By the end of this you will have built QuestLog: you type in a thing you have to do, it appears on screen, ticking it off awards points, and when you come back later it is all still there. Nothing is hidden from you. Every line is one you wrote and can watch running.

How this works

You write code in the page and press Run. When something goes wrong, and it will, the error says which line and what it was expecting. You fix it and run it again. Several steps let you scrub a program backwards and forwards one line at a time, watching each variable change as it happens. You need no setup and no previous programming.

The steps

Step 1

What an app is made of

Every app you have used, from a bank site to a game, is three separate things stacked on top of each other. Not three parts of one thing: three separate things, written in three different languages, each of which can be removed on its own.

There is the structure, which says what exists: a button here, a heading there, a box for typing in. There is the style, which says what it looks like: this colour, that size, rounded corners. And there is the behaviour, which says what happens when you touch it.

What "a language" means here, and why there are three

A language is a set of words a computer has been taught to obey. Structure is written in HTML, style in CSS, behaviour in JavaScript. They are separate because they answer separate questions, and because keeping them separate means you can change how something looks without any risk of breaking what it does.

This course is almost entirely about the third one. Behaviour is where the thinking lives, and it is the part that transfers: the ideas in the next thirteen steps are the same ideas in Python, in Java, in the language running inside a washing machine.

Lab 1 · Take a button to pieces
Try this firstPress Add 10 points once and watch the number above it go up. Then press Style: on so it reads off, and press the button again.
Predict before you flip. The question worth getting wrong: if you take away the style, does the button still work? Commit to an answer, then switch it off and click. Then take away behaviour and notice that the button still looks completely clickable. That gap, between looking right and doing something, is most of what a beginner gets confused by.
Looking finished and being finished are not related

With the style off and the behaviour on, you get grey unformatted text that works perfectly. With the style on and the behaviour off, you get a polished button that does nothing at all. A computer has no opinion about which of those is closer to done.

Lab 2 · Which layer is missing?
Try this firstRead Case 1 and press one of the three buttons. It says straight away whether that was the missing layer and why, then offers Next case.
Four broken things. Each one is missing exactly one of the three layers. Work out which, from the symptom alone, the way you would have to on a real screen where you cannot see the code. The last one is the sneaky one.
A friend sends you a link to the login page they have been building. It looks finished: spacing, colours, a proper heading, and a login button that changes colour when you move the pointer over it. You type your name and press the button and absolutely nothing happens. No error, no message, nothing. Which layer is missing?
Behaviour. The structure is there, or you would see nothing to press. The style is obviously there, since the button changes colour when you point at it, and somebody had to write that down. What is missing is anyone having written down what a click means. This is the single most common state for a half-built app to be in, because the first two layers are the quick ones.
Step 2

Your first line that does something

A program is a list of instructions, carried out in order, exactly as written. That last part is the hard bit. A computer will not work out what you meant, will not fill in an obvious gap, and will not quietly fix a spelling mistake. It does the thing you actually wrote.

Here is your first instruction. say is the word for "put this on the screen", and the round brackets hold the thing you want said.

Is say a real word, or one this page made up?

It is this page's word. JavaScript itself has no say. What a browser actually gives you is console.log("hello"), which writes a line into a panel called the console that you only see if you go looking for it. Everything else about the line is exactly real: the name of the job, the round brackets holding the material, the quotes, and the fact that it is carried out precisely as written.

The page uses say because output should be visible without a detour, and because the name says what it does. When you open a real browser console later, write console.log instead and everything you learned here still holds. A few more page words turn up as you go: ask() for reading the box on the page in Step 4, and load() and save() for the cupboard in Step 12. Each one stands for something a browser really does, and each is pointed out when it arrives.

What the brackets and quotes are actually for

The round brackets mean "do this, using what is inside". say("hi") is one instruction: the name of the job, then the material to do it with. Without brackets, say is just the word, in the same way that "kettle" is not the same as boiling one.

The quotes mean "the letters inside here are text, not an instruction". "hello" is five letters. hello with no quotes is a name, and the computer will go looking for something called hello and complain when there is nothing there. You will meet that error in a moment, on purpose.

Lab 3 · Make it answer you
Try this firstPress Run with the line that is already there. The word hello appears in the output panel under the editor.
Change the words and run it again. Then try the four buttons above the editor. Each one loads a line that looks nearly identical to the last, and each one behaves differently. Predict what each will print before you press Run. Getting one wrong here is worth more than getting all four right.
Quotes decide everything

say(2 + 2) prints 4, because without quotes those are numbers and a computer can add numbers. say("2 + 2") prints 2 + 2, because with quotes it is five characters of text and there is nothing to work out. Same keys on the keyboard, different meaning.

Two keyboard notes while we are here: a computer writes multiply as * and divide as /, because there is no x or division key that means only that. So 60 * 60 * 24 is sixty times sixty times twenty-four.

Lab 4 · Break it deliberately
Try this firstPress Run without changing anything. The program stops, and the panel underneath names the line it stopped on and what it could not find.
Read the errors. Each button makes a specific mistake, of the kind you are about to make by accident many times. The point is to see the message once now, while you know exactly what caused it, so that in twenty minutes you recognise it instead of panicking.
I do not understand what an error message is telling me

An error is not the computer telling you off. It is the computer stopping at the first thing it could not carry out and telling you where it stopped. It always includes a line number, and the line number is almost always right, even when the actual mistake is a missing bracket on the line above.

Read it in this order: which line, then what word it is complaining about, then what it says it expected. Three quarters of all errors you will ever see are a misspelled name, a missing bracket, or missing quotes.

You want to print the words hello on the screen. You write say(hello) and get an error saying there is no variable called "hello". What did the computer think you were asking for?
It went looking for a thing called hello. Bare words are names. The computer assumed you had stored something under the name hello earlier and wanted it printed, checked, found nothing, and said so. Add the quotes and there is nothing to look up, because the letters are the material itself.
Step 3

Boxes that remember

So far every program has forgotten everything the instant it finished. To build anything at all you need somewhere to put a value and a name to get it back by. That is a variable: a labelled box with something in it.

let score = 0 makes a box labelled score and puts 0 in it. After that, writing score anywhere means "whatever is in that box right now".

Why the = sign here does not mean "equals"

In maths, x = 5 is a statement about the world that is either true or false. In a program it is an instruction: put 5 into x. It is an arrow pointing right to left, and it is always an order, never an observation.

That is why score = score + 10 makes sense here and would be nonsense in maths. Work out the right-hand side first, using the current contents of the box, then put the answer back in the same box. If score held 30, the right side works out to 40, and 40 goes in.

Why do some programs say let and others say const?

Both make a labelled box with something in it. let makes one you are allowed to refill later, which is why let score = 0 followed by score = 100 is fine. const makes one that keeps whatever you first put in, and trying to refill it stops the program with the message "was made with const, so its value cannot be changed".

Use let for a box whose contents change, like a score. Use const for one that should not, like a list of quests that stays the same list all the way through. One catch worth knowing before Step 7: a const list can still have things added to it and taken out of it. What cannot change is which list the name refers to, not what is inside that list.

Lab 5 · Watch the boxes fill, one line at a time
Try this firstDrag the slider under the editor one notch to the right at a time. The highlighted line moves down and the boxes underneath show what each variable holds at that moment.
Drag the slider through the program. The highlighted line is the one about to run, and the boxes below show exactly what every variable holds at that moment. Drag backwards as well as forwards, which is called scrubbing, the same word as for a video. Stop on the score = score + 10 line and read the boxes: score still holds 0, because the highlighted line has not run yet. Take one step forward and it holds 10. In between, the right-hand side was worked out using the old contents, and only then was the answer put back into the same box.
A copy is a copy

let b = a does not tie the two boxes together. It reads what is in a right now and puts a copy in b. Change a afterwards and b does not care. This is obvious once you have seen it and surprising until then, so the quiz below is about exactly that.

One thing in the editor below is not an instruction. Anything on a line after two slashes, //, is a comment: a note written for a person, which the computer skips over completely. The tasks in this course use comments to tell you what to do, so a comment line never counts as one of your lines of program.

Lab 6 · Get score to exactly 100, in two lines
Try this firstPress Run before writing anything. It stops, because let score = has nothing after the equals sign yet.
Graded on the result, not the wording. Any two lines that leave score holding 100 will pass, and there are several. It checks what the program actually did, not whether you wrote it the way anyone expected.
What makes a name a good one

A name may use letters, numbers and underscores, and may not start with a number. Beyond that the computer does not care in the slightest: x, score and currentPointsThisRound are all equally valid.

The people who care are you, next week. A name is a note to your future self about why this box exists. Two words joined with a capital in the middle, like totalPoints, is the usual habit in JavaScript. None of this affects whether the program runs, which is exactly why it gets forgotten.

You run these three lines: let a = 1, then let b = a, then a = 5. What is in b at the end?
1. The second line ran once, took a copy of the 1, and finished. It did not make an arrangement about the future. When the third line changed a, nothing went looking for other boxes to update. Scrub Lab 5 through those three lines and watch it happen.
Step 4

Talking to whoever is using it

An app nobody can type into is a demonstration. The moment a program can take something in and answer differently because of it, it stops being a list of instructions and starts being a tool.

The box on the page is structure, from Step 1. It quietly holds whatever has been typed into it, and the behaviour layer can ask for that at any moment. Here the request is spelled ask(), so let name = ask() puts whatever is in the box into a variable called name. What comes back is always text, even when what was typed looks like a number and the second lab in this step is what that costs you.

How a typed letter gets from the keyboard into a variable

Nothing happens at the moment you type. A key press changes what the box is holding and stops there. No code of yours runs, and no variable moves. The box keeps the characters the way a piece of paper keeps what was written on it.

The value only travels when something asks for it. ask() takes a copy of what is in the box at that instant and hands the copy over. Type something else afterwards and the variable does not notice, for the same reason b did not notice in Step 3: it was given a copy, not a link.

Always text, which matters more than it sounds. Type 42 into a box and you get the two characters "42", not the number. Adding 1 to that gives you "421", because with text, plus means "stick together". The second lab in this step is that bug, on purpose.

Lab 7 · A greeting machine
Try this firstType a name into the box above the editor, then press Run. The greeting comes out in the output panel with that name in the middle of it.
Type a name, then press Run. Then empty the box completely and run it again, and read what it greets. Every real app has to decide what to do about nothing having been typed, and the honest default is usually not the one you get for free.
Lab 8 · The plus sign that will not add
Try this firstPress Run before changing anything. The box holds 20, the program tries to add five to it, and it prints Total: 205.
This program is wrong and you are going to fix it. It should add five to whatever number is typed in. It does something else instead. The fix is one word, and finding it is the point: Number(...) turns text into an actual number.
The same plus, two different jobs

With two numbers, + adds. With any text involved, + joins. So "5" + 1 is "51" and 5 + 1 is 6. Nothing warns you, because both are perfectly reasonable things to want.

A form asks for a number of points to award. Someone types 20. The code says let total = typed + 5 and prints 205. Why?
It joined instead of adding. Everything typed into a box arrives as text. "20" + 5 makes "205", and it is not an error, so nothing complains. Number(typed) + 5 gives 25. This single confusion is behind a remarkable number of real bugs in real software.
Step 5

Making a decision

Until now every program has done the same thing every time. A decision is where a program stops being a recipe and starts being able to handle a situation: check something, and take one path or the other.

The shape is always the same. if, then a question in brackets, then what to do when the answer is yes. Optionally else, and what to do when it is no.

if (age >= 18) {
  say("come in")
} else {
  say("go home")
}

The curly brackets hold the instructions that belong to each side. Anything inside the first pair happens only when the answer is yes, anything inside the second only when it is no, and whatever is written underneath the whole thing happens either way. The comparison signs work as they do in maths: >= is greater than or equal to, < is less than.

What counts as a question a computer can answer

Only questions with a yes or no answer. age >= 18 is one: it works out to either true or false, nothing else. > is greater than, >= is greater than or equal to, === is "exactly the same as", !== is "not the same as". Two questions can be joined into one with ||, two vertical bars, which means "or". The whole thing is true if either side is true, and the second side is not even looked at once the first one has said yes.

True and false are real values, as real as 7 or "hello", and you can put one in a box: let allowed = age >= 18 stores true or false. That turns out to be one of the more useful things in programming. It is why the comparison and the decision are two separate ideas rather than one.

Lab 9 · The bouncer
Try this firstDrag the Age slider down to 17 and see which of the two boxes at the bottom lights up, then drag it back up to 18.
Drag the age and watch the path light up. Stop exactly on 18 and check which way it goes, then change >= to > and stop on 18 again. One character, and one eighteen-year-old is now standing outside. Almost every off-by-one bug in the world is that character.
One equals sign is an order, three is a question

= puts a value in a box. === asks whether two things are the same. Writing if (age = 18) does not compare anything: it puts 18 into age, throws away whatever was there, and then treats 18 as a yes. It runs. It is wrong, which is the worst combination.

Lab 10 · Write the rule yourself
Try this firstPress Try age 12. The program runs with age already holding 12, and prints what label holds, which so far is nothing yet.
Tested against ages you did not pick. The age box is filled in for you before each run, so your job is only to put the right word in label. Use the Try age buttons as often as you like, then press Check against hidden ages. That runs your rule against a set of ages chosen to include every edge: both boundaries, one either side, zero, and something absurd. If a case fails it tells you which age and what your rule said.
Say "else if" another way

Think of a list of rules, checked from the top, where the first one that matches wins and the rest are never looked at. That is exactly what a chain of else if is.

Which is why order matters enormously. If you check "over 10" before "over 100", then 500 matches the first rule and stops, and your "over 100" branch can never run at all. Put the narrowest rule first. This is a real bug that is hard to spot, because every individual line looks correct.

This is meant to grade scores. if (s > 50) say("pass"), then else if (s > 90) say("top marks"). Someone scores 95. What gets printed?
pass. 95 is greater than 50, so the first branch runs and the whole chain is finished. The "top marks" line is unreachable for every score, not just this one: any number above 90 is also above 50. Swapping the two lines fixes it. Nothing in the code is misspelled, which is what makes this kind of bug expensive.
Step 6

Doing it again without saying it again

You could draw ten stars with ten lines of code. A hundred stars is a hundred lines, and now you have a real problem: not the typing, but that changing the star means changing it in a hundred places, and you will miss one.

A loop says "do this again while something is still true". Three parts: where to start, when to stop, and what to change each time round.

Reading the three parts of a for loop

for (let i = 0; i < 10; i++) reads as three separate instructions separated by semicolons. Start with i at 0. Keep going while i is under 10. After each pass, add one to i. That last part is what i++ means.

i is short for index, and counting from 0 rather than 1 is a convention you will meet everywhere in Step 7. With i < 10 and a start of 0, the body runs for 0 through 9: ten times, which is what was wanted, and never with i equal to 10.

What is the dot in drawn.length?

The dot means "reach inside this thing and use the part called that". drawn holds text, and every piece of text knows its own length, so drawn.length is the number of characters in it. If drawn holds "***" then drawn.length is 3. Read it left to right, as "the length of drawn".

The same dot turns up everywhere from here on and always means the same thing: quests.length in Step 7 is how many quests there are, and quest.title in Step 9 is the title of one quest. It is not doing anything clever. It is the difference between naming a box and naming one particular thing inside it.

Lab 11 · Step a loop round by round
Try this firstDrag the slider under the editor slowly from the left. A star joins the row just below the slider on every pass, and the narration at the bottom counts them as they arrive.
Slow it right down. Each pass adds a star and lights up the counter. Watch the last frame in particular: the loop stops on the pass where the test fails, and the counter has already reached ten by then, which is why the tenth star is drawn when i is nine.
The loop that never ends

If the stopping test can never become false, the program runs until something stops it. In a browser that means the page freezing. Here it means a message telling you what happened, because every loop in this page has a step budget. The lab below lets you cause one safely.

Lab 12 · Off by one, in both directions
Try this firstThe first of the four loops is already loaded. Press I say 5, then press Run, and the narration says whether the count matched.
Predict the count, then run it. Four loops that look almost the same. Commit to a number for each before pressing Run, and see which of the four catch you. Then use the last button to write a loop that never stops, and watch it get caught rather than hanging the page.
Lab 13 · Scratch pad
Try this firstPress Run to print the seven times table, then change the two 7s to another number and run it again.
Nothing is marked here. Somewhere to try things: a times table, a countdown, a triangle of stars, a loop inside a loop. Some suggestions are loaded above the editor if you want a starting point, but there is no right answer and nothing is checking.
How many times does the body of for (let i = 1; i <= 5; i++) run?
5 times. It starts at 1, and <= lets 5 through, so 1, 2, 3, 4, 5. Compare that with i = 0; i < 5, which also runs five times but with different values in i. Both are used constantly. Which you want depends entirely on whether you care about the count or the numbers.
Step 7

Holding many things at once

QuestLog needs to hold your quests, and there is no way to know in advance how many there will be. One box per quest cannot work. You need one box that holds a list, that can grow, and that you can walk through.

Square brackets make a list: let quests = ["Feed the cat","Do maths"]. Square brackets also get one item back out, by its position. Four things you will do to a list, written out:

say(quests[0])              // the item at position 0
say(quests.length)          // how many items there are
quests.push("Walk the dog") // add one to the end
quests.splice(1, 1)         // take out 1 item, starting at position 1

The dot is the same dot as in drawn.length in Step 6: reach inside the list and use the part called that. push and splice are the two you will use in QuestLog, and Lab 14 below lets you press them as buttons before you have to write them.

Why the first item is number 0

Positions count from zero, so the first item is quests[0] and the second is quests[1]. This trips up everyone at first and there is a real reason for it: the position is a distance from the start, not an ordinal. The first item is zero steps along.

The consequence you will actually use: a list of 5 items has positions 0 to 4, so the last item is always at length - 1. Asking for quests[5] in a five-item list is not an error, which is worse than if it were. You get undefined, and the trouble surfaces later, somewhere else.

Lab 14 · A list you can push around
Try this firstPress push to the end twice, then press remove position 0, and watch the numbers written under the boxes.
Watch the numbers, not the words. Add to the end, take off the end, then remove something from the middle and see every position after it slide down by one. That renumbering is the reason removing from the middle of a long list costs more than adding to the end, which is a whole course of its own later on.
Lab 15 · Walk the list and total it up
Try this firstPress Run on the loop already in the editor: it prints total 75. Then press The off-by-one and run that one.
The loop from Step 6, now with a purpose. Two ways to walk a list are shown: counting with an index, and asking for each item in turn with for ... of. Both are common. The second is easier to read and the first is the one you need when the position matters.
I need this said another way: length versus last position

Think of a row of five houses, numbered from 0. The number of houses is 5. The number on the last house is 4. Both numbers are useful and they are never the same number.

So list.length is how many, and list[list.length - 1] is the last one. If you ever write list[list.length] you get nothing back, because you have walked one house past the end of the street.

Lab 16 · Build the list the checker wants
Try this firstPress Run to see the starting list printed, then press Check the list to be told what it wants instead.
Any route that gets there passes. You are given a starting list and a target. Reach it however you like, with pushes, removals or a loop. It compares the finished list, not your method. One thing is ruled out: the list is made with const, so you cannot swap in a whole new list, and list = ["b","c","d","e"] stops with an error. Changing what is inside the list is allowed, and that is the exercise.
A list holds 4 quests. You write say(quests[4]) expecting the last one. What happens?
undefined, quietly. The four items sit at 0, 1, 2 and 3, so the last one is quests[3]. Asking for 4 is asking for a fifth item that does not exist. No error is raised, which means the wrong value travels onwards into your program and breaks something later, a long way from the actual mistake.
Step 8

Naming a recipe

Awarding points in QuestLog means three things: add to the total, save it, and update the screen. You will need that in four different places. Copying those three lines four times means that when the rule changes you have to find all four, and you will find three.

A function gives a name to a set of steps, so the four places all say the same short thing and there is exactly one copy of the actual work.

What goes in, and what comes back out

A function can take material in and hand a result back. The names in the brackets when you write it are parameters: empty slots. The values you put in when you call it are arguments, and they fill those slots for that one run.

return is how a result comes back, and it also ends the function on the spot. A function with no return still does its work and hands back undefined. That is the right choice when the point is the doing, like drawing on screen and the wrong choice when the point is the answer. A bare return with nothing after it is allowed too: it hands back nothing and simply stops there, which is how a function leaves early when there is no work to do.

Lab 17 · A function as a machine
Try this firstDrag the slider under the editor to the right, one notch at a time. Watch the IN slot of the machine fill with 5, then the OUT chute fill with 10.
Drag the slider through the run. Watch the argument drop into the slot, the body run with that value in place, and the answer come out of the chute. Keep going past the first call and notice the slot is empty again before the second one starts: each call starts clean, which is why a function can be trusted. To feed it something else, change the 5 in say(award(5)) and press Record a run again. One box in the panel below is worth a look: award holds the function itself, because a function is a value kept in a box like any other.
return hands back, say prints

These are not alternatives. say puts something on screen and the program cannot use it afterwards. return hands a value back to whoever asked, to store or add or test. A function that says its answer instead of returning it looks like it works and cannot be built on.

Lab 18 · Three copies, one function
Try this firstPress Run before changing anything. The pasted version already works and prints 30 points, 3 messages, and your job is to get that same line out of one copy of the work.
The rule is about to change. In the editor is working code with the same two lines pasted three times. Collapse it into one function and three calls, keeping the behaviour identical. Then press The rule changed and watch what each version costs you.
Lab 19 · Your Step 5 rule, as a function
Try this firstPress Run before writing anything. It prints undefined, because the body is empty and nothing is handed back. Then press Check against hidden ages, which names the first age it tried and what came back for it.
The same rule, now portable. In Step 5 you wrote the child, teen and adult rule against one age that had been set for you, and the answer landed in a variable. Wrap it in function stage(age) { ... } and hand the answer back with return, and any program can ask about any age it likes, as often as it likes. The checker calls your function with the same eight ages, boundaries included.
A function is written as function double(n) { say(n * 2) }. Someone writes let x = double(5) and finds that x holds undefined, even though 10 appeared on screen. Why?
Nothing was returned. Printing and returning are separate acts. The 10 went to the screen, where the program cannot reach it; the function itself handed back nothing. Change say(n * 2) to return n * 2 and x holds 10, though now nothing appears on screen unless the caller prints it. That division of labour is the point.
Step 9

Describing one whole thing

A quest is not one value. It has a title, whether it is done, and how many points it is worth. You could try to store one with the tools you already have: the titles in one list, the done marks in a second, the points in a third. On top of that, a promise to yourself that the third item of each list always describes the same quest. It works until the day you remove a quest from one list and forget the other two. From then on every quest after it is wearing somebody else's points, and nothing has gone wrong loudly enough to tell you.

An object holds named values together, so one variable is one whole thing: { title:"Feed the cat", done: false, points: 10 }.

Getting a value back out uses a dot and the name of the field: quest.title is the title, quest.points is the points. Setting one works the same way round: quest.done = true marks it finished. The dot is the same one as in quests.length; it reaches inside a thing and names one part of it. One shorthand shows up in the labs below: done already holds true or false, so it can be the whole question by itself, and if (q.done) means "if q.done is true". Writing if (q.done === true) says exactly the same thing and is not wrong, only longer.

How an object differs from a list

A list is things in an order, reached by number, where the order is the meaning: first, second, third. An object is things with names, reached by name, where the order does not matter at all.

Use a list when you have many of the same kind of thing and might need to walk through them. Use an object when you have one thing with several different aspects. QuestLog uses both together, and so does almost every real app: a list of objects.

Lab 20 · A character sheet that redraws itself
Try this firstChange level: 3 to another number and press Run. The card on the right is drawn again with what you wrote.
Edit the object, watch the card. Press Run after each change. The card on the right is not written anywhere as a card: every line of it was read out of the object on the left, which is how every screen in every app you use actually works. Delete the hp line and run it again, and see what the card does about a value that is not there.
Lab 21 · A list of objects
Try this firstPress Run: it prints earned 45 of 3 quests. Add up the points of the ones marked done: true yourself, and you get 25.
This is the shape of QuestLog, and this one is wrong. A list, holding objects, each with the same named fields. The loop counts every quest, finished or not, so it pays out for the maths nobody has done. Reach inside each one by name and total up only the ones that are done. Everything from Step 13 onwards is this.
What is the question mark in q.done ?"[x]" :"[ ]"?

It is an if and an else squeezed into one value. Read the question mark as "then" and the colon as "otherwise": if q.done is true the whole thing is "[x]", and if it is false the whole thing is "[ ]". It answers with a value instead of doing something, which is why it can sit in the middle of a line that is joining text together.

Written the long way it is four lines: let mark ="[ ]", then if (q.done) { mark ="[x]" }, then use mark. Both are correct and every checker in this course accepts either. The short form is worth being able to read even if you never write one, because other people's code is full of them.

Dot or square brackets?

quest.title and quest["title"] do the same thing. The dot is shorter and is what you will use nearly always.

The brackets earn their place when the name is in a variable: if field holds "title", then quest[field] reaches the title, while quest.field goes looking for something literally called field and finds nothing. That distinction matters the moment you write code that works on a field chosen at run time.

You have const q = { title:"Do maths", done: false } and write q.points. There is no points field. What do you get?
undefined. Asking for something that is not there is not an error, in objects or in lists. You get a value meaning "nothing here", and it travels on. If you then add 10 to it you get NaN, which stands for not a number, and that is usually the first symptom you actually notice, several lines from the cause.
Step 10

Making it react

Every program so far ran top to bottom and stopped. A real app mostly sits there doing nothing, waiting. Someone clicks, and a piece of your code runs. Then it waits again.

That is the behaviour layer from Step 1, finally connected up: you hand the browser a function and say "run this when that is clicked". The browser keeps it and calls it later, possibly never. A function handed over like that has a name of its own: a handler. It handles one kind of thing that can happen, and an app is mostly a pile of them, one for each thing a person can do. Handing one over is called registering it: you give it to the browser, the browser writes it down, and your part is finished until something happens.

Handing over a function without calling it

There is a real difference between award and award(). The first is the function itself, as a value you can hand to someone. The second is an instruction to run it right now.

So onClick(award) means "here is a function, keep it for later" and onClick(award()) runs it immediately and hands over whatever it returned, which is usually nothing. The button then does nothing forever. The brackets are the entire difference, and this catches everyone at least once.

There are two ways to hand a function over. If it already has a name, pass the name: onClick(award). If it is only ever needed here, you can write it on the spot with no name at all:

onClick(function () {
  points = points + 1
})

The words function () { ... } in the middle of a call mean "here is a small function, made right now, for you to keep". It is the same kind of thing as award, just never given a name because nothing else needs to call it. That is the form the two labs below use, and the form every handler in QuestLog is written in. onClick and onKey are this page's names for handing a handler over, in the same way say was in Step 2. A real browser spells the same act button.addEventListener("click", ...). Everything about how it behaves is the same.

Lab 22 · An event sandbox
Try this firstPress Run once, then press Click me in the panel headed The app, under the editor. Two lines appear in the event log below it.
Use the controls inside the app. Press Run once: that hands your two functions over and the program ends, and nothing is running after that. Now press Click me, press a, press b and press space. Each one is an event arriving, and each appears in the log underneath with a triangle in front of it, followed by whatever your handler did. Fire several in a row and read the log from the top: each handler runs all the way to the end before the next event is looked at. Nothing here ever happens at the same time as anything else.
Lab 23 · Only on the space bar
Try this firstPress Test with a run of keys before writing anything. It sends eight keys through your empty handler and reports that space came through without a point being awarded.
Ignore every key but one. Your handler is given the key that was pressed and has to award a point only for the space bar. It will be tested with a run of keys including space, and a wrong answer tells you which key got through that should not have.
A button is wired up with onClick(award()). What happens?
It runs once, at the wrong moment, then never again. The brackets mean "run it now", so the point is awarded while the page is still setting itself up, and what gets handed over is whatever award returned, which is not a function. Drop the brackets. The symptom, something happening once on load and never on click, is a useful one to recognise.
Step 11

Drawing the screen from data

Here is the idea that turns a pile of code into an app, and it is a smaller idea than it sounds. Keep the truth in one place, as data. Have one function whose only job is to draw the screen from that data. When the data changes, call it again.

The alternative, which everyone tries first, is to change the data and also reach onto the screen and patch the bit that showed it. That works until there are two places showing the same thing, and then you have two truths and no way to tell which is right.

What "the screen is out of date" actually means

Screen contents are just structure, from Step 1, and it stays exactly as it was put there. Change a variable and nothing on screen moves, because nothing is watching. The words on screen are a photograph of the data taken at the moment you drew it.

So a wrong screen has two possible causes, and telling them apart is most of debugging: either the data is wrong, or the data is right and nobody redrew. Printing the data is how you find out which, and it is why the lab below shows you both at once.

Lab 24 · Data on the left, screen on the right
Try this firstPress Tick the first quest and compare the two panes. The data on the left changes and the screen on the right does not.
Change the data without redrawing. The two panes disagree, and the screen is the one lying. Then press Redraw and watch them agree again. Turn on Redraw after every change and the disagreement becomes impossible, which is the whole trick.
Lab 25 · Write the drawing function
Try this firstPress Check against several lists before writing anything. The empty lines list comes straight back, and the message prints what it wanted instead.
One list in, one screen out. Given a list of quests, produce the lines of the screen: the title of each, marked if it is done, and a total at the bottom. It checks what your function produces for several different lists, including an empty one, which is the case everybody forgets.
A quest is ticked off. The data now says done: true, but on screen it still looks unticked. Where is the bug?
Nobody redrew. The screen is a photograph, not a window. It shows the data as it was when it was last drawn, and it does not change on its own. This is why keeping one drawing function and calling it after every change is worth the small amount of extra work: it makes this bug impossible rather than merely rare.
Step 12

Remembering after you close it

Everything so far lives for as long as the page is open. Close it and every quest is gone. A to-do app that forgets your to-dos is a curiosity.

Browsers give every app a small named cupboard to keep things in, which survives the page closing. It holds text and nothing else, so a list of quests has to be turned into text on the way in and back into a list on the way out.

Turning a list into text, and back

JSON.stringify(quests) takes your list of objects and produces one long line of text describing it exactly. JSON.parse(text) reads that text and builds the list again. Text goes in the cupboard; the pair of them get you there and back. JSON is just the name of that way of writing data down as text, and it is short for JavaScript Object Notation.

What comes back is a rebuilt copy, not the original. That matters the first time you save, reload, and find your changes going into a list nobody is looking at any more. The lab below lets you cause that.

What is null, and how is it different from undefined?

null means "deliberately nothing". load() hands back null when the cupboard has never had anything put in it, which is what happens the very first time anybody opens your app. So if (quests === null) reads as "if nothing was saved", and the line after it starts you off with an empty list instead of crashing on a value that is not a list.

undefined, from Step 7, is the other kind of nothing: nobody ever set this, the way asking a four-item list for position 4 gives you undefined. A rough rule that will serve you for years: undefined is nothing by accident, null is nothing on purpose. Both are ordinary values, and both can be compared with ===.

Lab 26 · Save, close, come back
Try this firstType a quest and press Add twice, then press Close and reopen. Both quests vanish, because nothing saved them.
The reload button is real. Add a few quests, press Close and reopen, and everything is gone, because nothing was saved. Add the save line and do it again. The panel at the bottom shows the actual text sitting in the cupboard, which is worth looking at: it is not a mystery format, it is your data with quotes round it.

The couple of lines at the top of a program that refuse to trust what came back have a name: the guard. Checking for null catches most of the rubbish, and one case needs something new. If what was saved is a single quest where a whole list was expected, for ... of cannot walk it. Asking whether something really is a list is spelled Array.isArray(quests). It answers true or false like any other question. So the guard can be:

let quests = load()
if (quests === null || Array.isArray(quests) === false) {
  quests = []
}

The || from Step 5 means "or": either problem on its own is enough to start fresh. Two separate if statements, one for each problem, would do exactly the same job.

Lab 27 · When what you saved is rubbish
Try this firstPress Somebody typed in it to spoil the saved text, then press Run. The program stops on line 2, which is a blank app for whoever that happened to.
Corrupt it on purpose. Edit the saved text by hand into something that is not readable data, then run it again. A real app cannot crash because of this: someone will always have half-written data from an old version. Add the guard above and watch it start cleanly instead. The checker tries six kinds of rubbish, including a quest with no title, which the guard does not cover and you will have to decide about yourself.
Your app saves correctly and loads correctly. You add a quest and reload, and the new quest is missing, though the older ones are all there. What is the most likely cause?
The save never ran. Loading works, so both halves are fine. What is missing is the save being called after that particular change. Older quests survive because they were saved by some other path that does call it. Every change that should last has to be followed by a save, which is a good reason to put both in one function, as in Step 8.
Step 13

QuestLog, all of it

Everything in the previous twelve steps was a piece of this. A list holding objects. A drawing function. Events for the buttons. A save after each change. Nothing new is needed, and the whole app is smaller than you would expect.

Two of the names in the lab are worth separating before you start. render() is yours: you write it, and it is what Step 11 called redrawing. draw(quests, points) is the page's. It is the thing that actually paints the screen, so it wants the list and the total. Everything the lab hands you is listed above the editor, with the shape of each one.

Reading a whole program instead of a line

Find the data first. One variable holds everything the app knows, and everything else exists to change it or show it. Then find the drawing function, which is the only thing that touches the screen. Then the handlers, one per thing a person can do.

Read in that order and any app makes sense, including ones far larger than this. Read top to bottom instead and it looks like a wall.

Lab 28 · Build QuestLog, checkpoint by checkpoint
Try this firstPress Check the checkpoints before writing anything. Checkpoint 1 goes red and says what it wanted the app to do.
Seven checkpoints, each verified against the running app. They start with a quest appearing on screen and end with points and saving. Each one tells you what to make happen rather than what to type, and checks the app's behaviour, so any working solution passes. The numbers in the comments in the editor are checkpoint numbers, so comment 4 is where checkpoint 4 wants work. Show me one that works is there if you get stuck, and reading a solution after trying is not cheating.
Lab 29 · Yours now
Try this firstPress Add to put a quest on the screen, then press tick on its row and watch the points at the top change.
Nothing is marked from here on. The finished app, and no checkpoints. Some things worth trying are listed, from small to properly hard. Break it as much as you like: the reset button puts the working version back.
In QuestLog, ticking a quest calls a function that sets done to true, saves, and redraws. Someone suggests removing the redraw to make it faster. What breaks?
The screen goes stale. The data is right and the save is right, and the app looks broken, which is Step 11 arriving in a real program. It is also the worst kind of bug to have shipped: it works fine while you test it, because you keep clicking other things that redraw, and it fails for the person who clicks just the one thing.
Step 14

Move from the course language to the browser

The small language in this course kept the important ideas visible. A browser uses JavaScript, and the same pieces have different names. say(text) can become output.textContent = text. onClick(fn) can become button.addEventListener("click", fn). The browser's document object model, or DOM, is the set of objects that represents the page.

JavaScript normally runs one piece of your code at a time on the main thread. Clicks, timers and completed network requests wait in queues. The event loop takes a ready task when the current call stack is empty. The browser can then update layout and paint the changed screen.

What happens to the page during a long loop?

A long calculation keeps the main thread busy. Click handlers cannot run and the browser cannot paint intermediate DOM changes, so the page appears frozen even though the loop is still executing.

Break large work into chunks, move suitable computation to a Web Worker, or use a server. Do not add a timer merely to hide a calculation that still blocks for the same total time.

Is the DOM the same thing as the HTML file?

The HTML file is input. The browser parses it into DOM objects, and JavaScript can add, remove or change those objects. Saving the original file does not automatically save later DOM changes.

Browser developer tools show the current DOM, the console, network requests and storage. They are the first place to inspect when the screen differs from the data you expected.

Lab 30 · Schedule clicks, timers and paint
Try this firstQueue a click, a timer and a short calculation. Step the event loop. Then make the calculation long and see which visible updates have to wait.
Watch the call stack before the task queue. A queued event is ready to run, but it cannot interrupt JavaScript that is already on the main thread.
A loop runs for five seconds on the main thread. Why does a button stop responding?
The click is queued. The event loop cannot start its handler while the call stack is occupied by the loop.
Step 15

Ask another program for data

An application programming interface, or API, is an agreed set of requests and responses. A browser might send GET /api/quests to read quests or POST /api/quests with data for a new one. The path, method, fields and possible errors form a contract between client and server.

A network answer arrives later. JavaScript represents that future result with a Promise. The words async and await let a function pause without blocking the event loop. The screen still needs explicit loading, success, empty and error states while the request changes.

async function loadQuests() {
  show("Loading…")
  const response = await fetch("/api/quests")
  if (response.ok === false) throw new Error("Request failed")
  const quests = await response.json()
  draw(quests)
}
Does a completed fetch mean the request succeeded?

It means an HTTP response arrived. A server can respond with 404 for a missing resource, 401 when no valid identity was supplied, or 500 when it failed internally. Check the status before trusting the response body.

A network failure, timeout and server rejection need different messages. Preserve enough information for a retry, but do not keep an endless spinner on screen.

When is retrying safe?

Reading can usually be retried. Repeating a write may create two orders, payments or quests unless the operation is idempotent. An idempotency key lets the server recognise a repeated submission and return the first result.

Wait longer between repeated attempts and add a little random variation when many clients may retry at once. The reliability course develops backoff, timeouts and retry budgets in detail.

Lab 31 · Drive every request state
Try this firstRun the success case, then choose slow, missing, malformed and offline responses. Add a timeout and compare a safe read retry with a repeated write.
Check what the screen says in every state. A useful error identifies what failed and offers an action the person can take, such as retrying or correcting a field.
A fetch receives HTTP status 500. What should the client conclude?
A response and a successful result are different facts. Inspect the status and handle the error state instead of treating every body as application data.
Step 16

Put shared rules on a server

Browser storage belongs to one browser profile. A server gives several devices and people a shared place to apply rules and store data. The server must treat every request as untrusted, even when your own client sent it, because anyone can construct a request without using your screen.

A database keeps records after the server process restarts. A schema describes fields, types and relationships. A transaction groups changes that must succeed or fail together. If two requests update the same quest, the server also needs a rule for concurrency, such as a version number checked on every write.

Why not put the database password in browser JavaScript?

Every browser receives that file and can read it. A secret sent to a client is no longer secret. Keep database credentials and private service keys on a server, then expose only the operations clients are permitted to request.

Public identifiers, such as an API base URL, are not secrets. Classify each configuration value instead of hiding every setting or publishing every credential.

What is a database migration?

Applications change their stored shape over time. A migration is a numbered, reviewed change that adds or transforms schema in a repeatable way. Production data may be older than the current program.

Plan how old and new application versions overlap during deployment. The Inside a Database course follows indexes, transactions, logs and recovery below the API layer.

Lab 32 · Protect a quest from a lost update
Try this firstLet two devices read version 3. Save from both without a check and observe the lost title. Reset, require the expected version, and resolve the conflict.
The version check turns silent data loss into a visible conflict. The application can reload, merge fields when that is safe, or ask the person which version to keep.
Why must the server validate a quest title when the browser already validated it?
The client is not a security boundary. Client validation helps the person correct a mistake quickly. Server validation protects shared data and rules.
Step 17

Separate identity from permission

Authentication answers “who is making this request?” Authorization answers “may that identity do this operation on this resource?” Logging in does not grant access to every account. The server checks authorization for every protected read and write.

After login, a server may place an unpredictable session identifier in a secure cookie. The server maps it to an account and expiry time. Tokens are another way to carry signed claims, but a server receiving one must verify its signature, issuer, audience and time limits before using those claims.

How should passwords be stored?

Do not store the password or a fast ordinary hash. Use a password-hashing function designed to be slow and memory costly, with a fresh salt for each account. At login, hash the candidate using the stored parameters and compare the result.

Multi-factor authentication reduces the damage from a stolen password. Account recovery deserves the same review as login because an easy recovery path can bypass stronger sign-in checks.

Is hiding a button an authorization check?

No. Hiding controls can make the interface clearer, but the request can still be sent directly. The server must decide using the authenticated identity, requested action and resource ownership or role.

Deny by default. Grant the smallest permission needed, record sensitive changes, and make expired or revoked sessions stop working.

Lab 33 · Check an access decision
Try this firstTry reading and editing as the owner, another signed-in person, an administrator and an expired session. Then hide the button and send the request anyway.
Read the decision inputs shown by the simulator. A useful authorization test names the identity, action and resource; “the person is logged in” is not enough.
A signed-in user changes a URL to another person's quest ID. What stops the read?
Authorization belongs on the server. The resource identifier is input, not evidence of ownership.
Step 18

Treat input as data, not instructions

A quest title may contain punctuation, emoji or text that resembles HTML. If the app assigns it with textContent, the browser displays those characters. If it builds an HTML string and assigns innerHTML, tags can become page structure and scripts may run. That is cross-site scripting, usually shortened to XSS.

Use the output operation that matches the context: text nodes for text, parameterized queries for database values, and carefully checked URLs for navigation. Input validation enforces the application's data rules; output encoding prevents data from becoming syntax. One does not replace the other.

What do HTTPS and browser security each protect?

HTTPS encrypts traffic in transit and authenticates the server name. It does not repair an XSS bug, stop an authorized server from collecting too much data, or make a stolen session harmless.

Cookies can be restricted with Secure, HttpOnly and SameSite attributes. A content security policy can limit which scripts and other resources a page may load. These are layers, not substitutes for safe code.

What should the app collect and log?

Collect the least personal data needed for the feature and keep it only as long as required. Access controls, deletion rules and backups must agree. Do not put passwords, session tokens or private quest text into ordinary logs.

Threat modelling asks what is valuable, who might misuse it, where trust changes, and how the design prevents or detects that misuse. Revisit the model when a new data flow is added.

Lab 34 · Render a hostile quest title safely
Try this firstRender the supplied title as HTML, then as text. Inspect the DOM result and the action the simulated browser would take. Try a SQL-like title too.
The safe result preserves the exact title as text. It does not delete ordinary angle brackets or guess which tags are friendly; it avoids interpreting the title as markup at all.
A title is limited to 80 characters. Is it now safe to insert with innerHTML?
Validation and output handling solve different problems. Render untrusted text as text, even when it also meets every length and shape rule.
Step 19

What happens when someone opens it

You have an app. Someone types its address, or taps a link, and a second later it is running on their machine, which may be on the other side of the world. That last part is worth understanding, because it is simpler than most people assume.

What a web address actually asks for

An address has a machine name and a path to a file on it. The browser finds the machine, asks it for that file, and the machine sends the file back. That is the whole conversation. It is the same conversation for a photograph or a font.

Then the browser reads what came back and finds it mentions other files: a stylesheet, some JavaScript. So it asks for those too, one request each. Your app arrives as a handful of separate files and is assembled where it will be used, not where it was written.

Three words the lab below uses. The machine that keeps the files and hands them out when asked is a server, which is all the word means: it serves files. Each of the three layers from Step 1 lives in its own file, and the ending says which: .html for structure, .css for style, .js for behaviour. A .css file is usually called a stylesheet, which is the same thing under a longer name.

Lab 35 · Follow one request
Try this firstPress Next five times, one press at a time, and watch the file pills at the top change from "not yet" to "arrived" while the screen below fills in.
Step through it. One address, and every request it turns into. Watch the structure arrive first, then the style, then the behaviour, and see the page go from nothing to plain text to finished as each one lands. This is also why a page sometimes flashes unstyled for a moment on a slow connection.
Lab 36 · Where to go next
Try this firstPress A tip calculator. The panel underneath says what building it would teach you and which steps cover it.
Pick by what you want to build. Each project says what you would learn from it and which course covers that ground properly. They are ordered by how much they ask of you, not by how impressive they sound.
Your app is one page mentioning one stylesheet and one script. How many requests does a browser make to open it?
Three, and in that order. The browser cannot ask for files it does not yet know exist. It fetches the page, reads it, discovers two more files, and asks for those. This is why the structure lands first and can briefly be visible with no styling at all.
Using a coding model without handing it the decision

A coding model can draft a function, explain an error or suggest a test. Treat the result as a patch from someone who has not seen the whole program: state the requested behavior, inspect the changed lines, run executable tests, and reject unrelated changes.

The model should receive only the files and permissions needed for the task. Generated code is not evidence that the program works; a passing check against the specification is. The Software Construction review lesson turns those rules into an interactive patch gate.

What you can do now

  • Store data in variables, lists and objects, and choose sensibly between them.
  • Make decisions and repeat work, and recognise the off-by-one at the boundary.
  • Write a function that takes something in and hands something back.
  • Keep one copy of the truth, draw the screen from it, and redraw after every change.
  • React to what someone does, and keep their data after they leave.
  • Read an error message and work out where to look.

Where this goes

  • Data Structures. You used a list and reached in by position. There are other shapes, and choosing the wrong one is how a fast app becomes a slow one. Starts with why removing from the middle of a list costs what it does.
  • Software Construction. Your code works. This is about code that still works after someone else changes it: specifications, tests, and hiding a representation so it cannot be misused.
  • Build a Microprocessor. Every line you wrote became instructions for a processor. That course builds one from a light switch upwards, and the two ends of the same story meet.
Step 20

Make the app work for different people and screens

Accessibility starts with ordinary HTML that carries meaning. Use a button for an action, a label for an input and headings in a sensible order. Those elements already support keyboards and expose useful roles and names to assistive technology. A clickable div has none of that behaviour unless you rebuild it.

A visible focus indicator shows where keyboard input will go. Instructions and errors must not depend only on colour, position or sound. When content changes without moving focus, a suitable live region can announce the update. Test with a keyboard and a screen reader instead of guessing from the markup.

What makes a layout responsive?

Let text wrap, let flexible columns stack, and keep controls large enough to operate. Test narrow and wide viewports, enlarged text, long translations, zoom and both pointer and keyboard input.

A media query changes rules when space or user preferences change. Respect reduced-motion and colour scheme preferences, but do not remove information when an animation is disabled.

Where do language and culture affect the program?

Keep interface text outside business logic and mark the document language. Use locale-aware formatting for dates, numbers and plural forms. Do not join translated sentence fragments in a fixed English order.

Right-to-left scripts can change layout direction. Names, addresses and calendars also vary. Store the underlying value separately from its formatted display.

Lab 37 · Audit a quest form
Try this firstRun the audit, then repair the missing label, keyboard trap, colour-only error and narrow-screen overflow. Turn on 200% text and reduced motion.
Each reported issue names a person, an operation and the broken condition. Keep that detail in the bug report so a visual adjustment does not hide the original access problem.
Why is a real button usually better than a clickable div?
Semantic elements carry working behavior. Use ARIA to describe missing semantics when necessary, not to replace a native element that already solves the problem.
Step 21

Test behavior and keep changes reversible

A useful test supplies an input, observes a result and explains the rule that would be broken by a failure. Unit tests isolate a function. Integration tests join parts such as an API route and database. End-to-end tests operate the application through a browser. Use the smallest level that can catch the failure honestly.

Debugging begins with a reproducible case. Read the first relevant error, inspect the actual state, reduce the input and change one assumption at a time. Logs and breakpoints provide evidence; random edits make the experiment harder to interpret.

What does version control add?

Git records snapshots and the parent relationship between them. A focused commit states one reason for change, includes its tests and can be reviewed or reverted without discarding unrelated work.

Branches let changes develop separately. A merge combines histories and may need a person to resolve places where both sides edited the same lines. Commit before a risky experiment.

What should be tested after a coding model writes a patch?

Start from the requested behavior and likely failure modes, not from the patch's own explanation. Inspect every changed line, run the existing suite, add a regression test and check permissions, data handling, dependencies and unrelated edits.

A model can propose tests, but a test that repeats the same mistaken assumption proves little. Include boundary cases and, when possible, compare against an independent reference or property.

Lab 38 · Find a regression with tests and commits
Try this firstRun the unit, integration and browser tests. Use the failing evidence to narrow six commits to the first bad one, then choose a regression test.
The best regression test fails before the repair and passes after it. Keep the input small enough that a future failure points back to the same broken rule.
A bug appears only when the API and database are connected. Which first test level fits?
Test across the boundary where the failure occurs. Add smaller unit tests too when a specific rule can be isolated.
Step 22

Cache files and keep useful work offline

A cache keeps a reusable response near the browser. HTTP cache rules can reuse versioned scripts and images without asking the origin for every visit. Files that change at the same URL need validation or short lifetimes; files named with a content hash can be cached for much longer.

A service worker can intercept requests and choose cached or network responses. That makes an offline shell possible, but it also introduces another stored version of the app. Update logic must avoid serving old code beside a new data format.

What happens to edits made while offline?

Store each pending operation locally with a unique ID, creation time and sync state. When connectivity returns, submit it idempotently. Mark it complete only after the server confirms the result.

If the remote record changed too, use an explicit conflict rule. Some fields can merge; others need the person to choose. “Last writer wins” is simple but can silently discard work.

How do caches become incorrect?

A cache key that omits the signed-in user or language can return one person's data to another. Sensitive responses often need private or no-store rules. Shared caches must vary on every request property that changes the response.

Purging every cache after every change is safe but loses the performance benefit. Version immutable files and give changing documents deliberate validation rules.

Lab 39 · Choose network, cache or offline queue
Try this firstLoad the app twice, deploy a new script, then go offline and add a quest. Reconnect and compare a versioned cache with a stale fixed URL.
Read the source and version for every result. A fast response is useful only when the cache key and freshness rule make it the correct response.
Why can app.4f2a.js be cached longer than app.js?
The name identifies the content version. A new build uses a new URL, while existing cached bytes remain correct for the old one.
Step 23

Deploy carefully and diagnose failures

A deployment should build the same reviewed revision that passed automated checks. Configuration selects environment-specific addresses and secrets; it should not require editing source code. Database changes need an order that lets old and new application versions overlap during rollout.

Observability supplies evidence after release. Logs record discrete events, metrics summarize values over time, and traces connect work across services for one request. Include request IDs and useful context, but exclude secrets and unnecessary personal data.

What should happen during a bad release?

Stop the rollout, limit further damage and restore a known working version when that is safe. A rollback may not reverse data changes, so migrations need forward and recovery plans.

After service returns, write a timeline based on evidence, identify contributing system conditions and add changes that improve prevention, detection or recovery. Do not stop at the last person who touched it.

How do rate limits and circuit breakers help?

A rate limit protects finite capacity and can slow abuse. It should define a scope, time window and useful response. A circuit breaker temporarily stops calls to a repeatedly failing dependency so work does not pile up behind it.

Retries consume capacity too. Limit attempts, use deadlines and make writes idempotent. Continue with Reliable Systems and Fault Tolerance for deeper failure models.

Lab 40 · Retry one write without creating it twice
Try this firstSubmit a quest while the reply is lost. Retry without an idempotency key, reset, and repeat with one. Inspect the log, metric and trace for both runs.
A lost response does not tell the client whether the server changed state. The stable operation key lets a retry recover the original result instead of guessing or creating a duplicate.
A request times out after a write was sent. What is known?
A timeout describes the client's observation. Design the operation so a repeated request can identify and return the earlier result.
Step 24

Plan the next application from evidence

Start with one person, one problem and one observable outcome. Write a few acceptance examples before choosing tools. Draw the data and trust boundaries, then build a thin route through interface, server and storage. Put it in front of a real user before widening the feature list.

For each release, keep a checklist that covers behavior, accessibility, security, privacy, performance, failure recovery and deployment. Record what was measured and what remains an assumption. A small app with clear limits is a better engineering result than a large one nobody can verify.

Where can AI help in a 2026 development workflow?

Use a coding model for bounded work: explain unfamiliar code, draft tests, propose a small patch, compare alternatives or search a codebase. Give it the relevant contract and minimum access needed. Keep generated changes in a reviewable diff.

Run the program and independent checks. Review security boundaries, dependencies, data migration and accessibility yourself. For uncertain behavior, make the model produce a test or source you can verify instead of accepting a confident explanation.

Which course should come next?

Software Construction develops specifications, invariants, test design, abstraction and review. Build a Language explains how source becomes tokens, syntax and machine instructions.

Use Inside a Database for storage, Performance Engineering for measurement, and Memory Exploits for low-level security boundaries.

Lab 41 · Build a release plan
Try this firstChoose a project, then answer the data, trust, offline and failure questions. The planner will order a thin first release and name its tests and gates.
Export the plan and challenge its assumptions before writing more code. Every milestone should leave one runnable path and a concrete check for the new risk it introduces.
What is the best first milestone for a larger application?
Build a thin working path. It tests the risky boundaries early and produces evidence before the design expands.