EverProduct
Frontend

Stage 05 · The Craft

The Net: Types as Documentation That Can't Lie

Every function you write already has a type — you just kept it in your head, where nothing checks it and it slowly becomes untrue.

You write user.adress. One missing letter. JavaScript is perfectly happy: the property doesn't exist, so the value is undefined, and the page renders an address line reading "undefined" — on Friday, in production, in the checkout.

Or a function takes an id and a name, someone calls it with the name and the id, and both are strings so nothing objects. The record is saved with the fields swapped, and you find out from a customer.

Both bugs share a property: they're not hard, they're just invisible until runtime. That's the category TypeScript removes.

What it actually is

TypeScript is JavaScript plus annotations, checked before your code runs and then erased. The browser never sees a type; the output is ordinary JavaScript. There's no runtime cost and no runtime protection — the checking all happens at build time, in your editor, as you type.

Which leads to the framing that makes it click: you already know the types. When you write a function, you know it takes a user object with an id and a name and returns a string. That knowledge currently lives in your head, where nothing verifies it and where it quietly stops being true three months after someone renames a field.

A comment describes what someone believed once. A type is checked every time you build.

The parts that earn their keep

You don't need much of the language to get most of the value.

Annotate the edges, let inference do the middle. Function parameters and return types are worth writing; local variables almost never are, because TypeScript already knows that a string assigned to a variable makes it a string. Over-annotating is a beginner habit that adds noise and no safety.

Describe your data shapes once. A User, an Order, a Product — written down in one place and referenced everywhere. This is where the "documentation that can't drift" claim gets real: rename a field and every place that used it lights up in red immediately, rather than in an error report next month.

Unions are the good part. A value that is exactly one of 'loading' | 'error' | 'empty' | 'success' gives the state article's "make illegal states unrepresentable" its teeth: now the compiler enforces it, and it will tell you when you've forgotten to handle one of the four cases. The same for variants, roles, statuses — anywhere you'd otherwise have used loose strings and a comment.

Strict null checking is the single biggest win. With it on, a value that might be absent must be declared as such, and you can't touch it until you've checked. That one setting eliminates most of the "cannot read properties of undefined" family, which is the most common error in frontend by a wide margin.

Generics, without the fear. All they mean is "this works with any type, and it remembers which one." A list of users comes back as a list of users, not a list of anything. You'll use them long before you write them, and when you do write one it's usually a single letter in angle brackets.

The escape hatches, and their honest cost

any turns checking off for that value. It's occasionally pragmatic and always a hole: everything downstream of an any is unchecked too, and a codebase with a few casually placed anys can have very little real coverage while looking typed.

unknown is the honest version: "I don't know what this is", and the compiler forces you to check before using it. When you're tempted by any, unknown is usually what you meant.

Type assertions — telling the compiler "trust me, this is a User" — are promises rather than checks. Nothing verifies them. They're occasionally necessary and are the most common way a "fully typed" codebase turns out to be lying.

The boundary: where types are only a hope

This is the misunderstanding that undoes people, so it deserves stating flatly: declaring a type for data from a server doesn't make the data match.

Types are erased. The compiler checked your code against a shape you asserted; it never saw the response. If the API returns null where you promised a string, TypeScript is silent and the crash happens exactly where it would have without types.

So the edges of your application — network responses, localStorage, URL parameters, form input, third-party callbacks — are where types stop being guarantees and start being assumptions. Two honest options: validate at the boundary with a runtime schema library, so the shape is checked once when it enters and typed with confidence thereafter; or accept the assumption knowingly and handle the failure. What isn't honest is believing an interface declaration protects you from an API you don't control.

The real payoff is the loop

Ask anyone who's used TypeScript for a year what they'd miss most, and the answer usually isn't the errors caught. It's the editor.

Autocomplete that knows what's actually on this object. Rename that updates ninety-four usages correctly. "Find all references" that's accurate. Instant red underlining the moment a call no longer matches its function — before you save, before you reload, before you've forgotten what you were doing.

That's a feedback loop measured in milliseconds, on a task you perform hundreds of times a day. The How to Learn sphere's account of deliberate practice — immediate signal, immediate correction — describes tooling as much as it describes study; and this is the tightest feedback loop available anywhere in this roadmap.

It also changes how you read unfamiliar code: with types, you can understand a function's contract without reading its body, which is most of what makes a large codebase navigable.

The costs, honestly

There's a build step and a configuration. There's a learning curve, and it has a genuinely frustrating middle where you know enough to want a complicated type and not enough to write it. There are libraries whose types are worse than their documentation. And there's a specific failure mode worth naming: spending an hour on an elaborate type to save five minutes of runtime checking — the type system becoming a puzzle you play instead of the work.

And it isn't always worth it. A fifty-line script, a one-off page, a prototype you'll throw away in a week — plain JavaScript is fine, and saying so is not heresy. The value scales with how long the code lives and how many people touch it, which is why almost everything professional is typed now.

How to learn it

Turn strict mode on from day one. Learning with it off and enabling it later means rewriting; and the null checking that strict mode brings is the main thing you came for.

Read the errors as sentences. TypeScript errors are famously long, and the skill is finding the first real mismatch: usually the innermost "type X is not assignable to type Y" near the end, which names the two things that actually disagree. Once you can read them, the compiler becomes a tutor that answers instantly and never gets tired.

Type an existing project rather than learning types abstractly. Take something you wrote in JavaScript and add types to it. Every error it finds is a real bug or a real ambiguity in code you already understand — which is far better material than exercises, because you have the context to judge what the error means.

Don't chase perfect types. If a type is taking more than a few minutes, use a simpler one and move on. The value is in the ninety percent that's easy.

In practice

Annotate function parameters and return types; let everything else infer.

Replace boolean combinations with a union of the states that actually exist. Then let the compiler tell you which case you forgot.

Grep your project for any and fix the three worst. Each one is a hole with everything downstream flowing through it.

Validate one API response at the boundary with a schema, and notice how much of your defensive code disappears once the shape is guaranteed at the entrance.

Use rename and find-references deliberately for a week. It's the fastest way to feel what types buy you, and it changes how willing you are to change your own code — which is worth more than it sounds.

Check yourself

Close the article and answer in your own words:

  1. What happens to types at build time, and what does that imply about runtime safety?
  2. What should you annotate, and what should you leave to inference?
  3. How does a union type give "make illegal states unrepresentable" real force?
  4. What does strict null checking eliminate?
  5. What's the difference between any and unknown, and why is an assertion not a check?
  6. Why doesn't declaring a type for an API response protect you, and what are the two honest options?
  7. What's the biggest day-to-day payoff, and why is it a learning argument as much as a productivity one?

In short

  • Types are the knowledge you already hold about your own functions, written where something checks it.
  • TypeScript is checked at build and erased: no runtime cost, and no runtime protection.
  • Annotate parameters and return types, describe data shapes once, and let inference handle the rest.
  • Unions turn the status pattern into something the compiler enforces, including telling you which case you forgot.
  • Strict null checking removes most of the "cannot read properties of undefined" family — the most common error in frontend.
  • any switches off checking for everything downstream; unknown is the honest version, and assertions are promises rather than checks.
  • Types stop at the boundary: validate network and storage data at runtime, or know you're assuming.
  • The real payoff is a millisecond feedback loop in the editor — accurate autocomplete, safe renames, errors before you save.