Trusting the data
Real programs read data from outside: a server, a file, a form. It arrives as text, often JSON. This server sends {"name":"Ada","age":"36"}. Look closely: "36" is in quotes, so age is a string.
JSON.parse turns the text into a value, but its type is any, so tsc checks nothing about it. Then as User tells tsc: trust me, this is a User. tsc agrees and finds 0 errors.
tsc writes app.js with the types removed, and Node.js runs it. Nothing checks the data now. age is really the text "36", and "36" + 1 joins text, so it prints 361, not 37.
Making tsc ask
Give outside data the type unknown, meaning "could be anything". Now tsc refuses data.age until you check it: error TS18046, 'data' is of type 'unknown'. You see the bug before anything runs (tsc still writes app.js unless noEmitOnError is on).
isUser is a type guard: a function that checks a value while the program runs, using typeof and in. Its return type, x is User, tells tsc: when this returns true, x really is a User.
Line 4 now calls isUser(data) inside an if. In that branch tsc narrows data from unknown to User, so data.age is allowed. Line 5 handles everything else. tsc finds 0 errors.
At run time
Now it runs. The guard checks each field. age is "36", a string, not a number, so isUser returns false. The else branch prints "bad data". The wrong value never reaches the maths.
When the server sends age as 36, with no quotes, it is a number. Every check passes and isUser returns true. Inside the if, data.age + 1 is 36 + 1, so it prints 37.
In real projects
In real projects, every place data enters gets a check like this: server replies, files, forms, saved settings. Past that edge, the types can be trusted. Libraries such as Zod build these guards for you from a schema.
What happens
;; step 1/9 · outside
the server replies with text (JSON):
{"name":"Ada","age":"36"}
"name": "Ada" a string
"age": "36" in quotes: a string, not a number
1 type User = { name: string; age: number };
> 2 const text = '{"name":"Ada","age":"36"}';
In a real app the text on line 2 comes from a server (for example with fetch) or a file. Here it is written into line 2 so the whole program runs as it is. Every output shown is real: tsc 5.9 with --strict, then Node.js. The cast version prints 361; the guarded one prints bad data, and 37 once the data is right.