Skip to content
teach

Lesson 49. Trusted to Make the Call

Mission link: The mission's last clause, trusted to make the call and to explain it to someone else, is what this lesson has to leave you able to do, since there is no further lesson left to hand the job to.
Primary source: TypeScript Design Goals
Prerequisites: Lesson 48, Lesson 11

Warm-up

  1. ▢ Lesson 45 showed a strict consumer's own settings unable to fix a loose author's claim, and lesson 35 showed a hand-written declaration lying about a return type with nothing at run time left to catch it. State, in one sentence, what the two demonstrations have in common.
Check

Both are the same fact seen from different angles: a type is a claim, checked once, at compile time, against a program that is then thrown away, and once that check is done nothing enforces that the claim was actually true. Lesson 35 varies who wrote the wrong claim, lesson 45 varies which project's settings produced it, and neither variation touches what the claim is worth once the program is running.

Know this

The same fact, seen six times

Six times now, this arc has shown you one fact in a different shape, worth naming plainly because a reader who has internalised it is most of the way to senior already. A type is a claim, checked against a program that is then thrown away. Lesson 2 met it from the quietest angle, a readonly parameter that constrains only the code the compiler reads. Lesson 11 named the trade behind it, soundness declared a non-goal on purpose. Lesson 29 read it off a real build's output, every interface, brand and assertion gone. Lesson 35 made it hurt, a hand-written declaration that compiled clean and failed at run time. Lesson 45 widened the blast radius, a consumer's own strictness unable to touch a claim a looser author had already published. Those five demonstrated the fact; this lesson is the sixth time and the only one that states it outright: the check happens once, against a program the compiler is about to stop watching, and everything after is only as honest as whoever wrote the claim.

What the compiler was never going to do for you

Judgment is what is left once the compiler has done everything it can, so it helps to say precisely where its ability ends. It cannot check a value that arrives at run time, because a network response, a file, or a caller who never ran a type checker was never inside the program it compiled; lesson 30 surveys those places and the honest type, unknown, that admits the gap. It cannot make an assertion true; as and ! compile to nothing and change only what the compiler believes, and lesson 18 catalogues what each one costs once that belief is wrong. It cannot tell a good abstraction from a bad one; tsc --diagnostics hands you an instantiation count and a check time, real numbers, but neither says whether a conditional type is worth what it costs the colleague reading its error, a measurement and a judgment lesson 42 kept deliberately separate. And it cannot decide what your domain permits; it will happily accept four states from two optional fields when the domain produces two, because counting states is arithmetic and deciding which are illegal is not, the gap lesson 24 taught you to close by hand. Four boundaries, four lessons, one shape underneath: the compiler enforces whatever you told it, precisely and tirelessly, with no opinion about whether you told it the right thing.

The three decisions that recur

Look back across the lessons behind those four boundaries and the same three questions keep recurring, in different code every time, worth carrying forward as a standing checklist rather than three unrelated topics. Where does this value enter, and what checks it. That is lesson 30's question, and the answer is never "the type", because a type at a boundary is a hope until a check produces it honestly from a result. What does this type permit that the domain does not. That is lesson 24's question, answered by counting: presence and absence multiply, a union's arms add, and the gap between what the count allows and what the domain produces is what is worth turning into a smaller type. What does the caller write, and what do they see when they get it wrong. That is lesson 41's question, and the one most easily forgotten, because a signature can satisfy the first half, inference asking nothing of a caller, while failing the second badly enough that the win stops mattering: an error naming your own machinery instead of the caller's actual mistake. None of the three has a universal answer; each is answered by looking at one call site, one boundary, one domain rule, and deciding, which is why they recur instead of resolving once and staying resolved.

How to explain a call to someone else

The mission's last clause asks for more than the right call, it asks you to explain it, and that has a shape worth learning on purpose. State the constraint that decided it: not "I preferred this" but the fact that ruled out the alternatives, a boundary that has to stay unknown because the payload comes from a queue you do not control. State the cost accepted: what the decision gave up, a parse skipped because narrowing to the two fields actually read was cheaper. State the thing that would change your mind: the fact that, arriving tomorrow, would make you undo the decision without an argument, the payload starting to arrive from outside your own build step. That third part is the one people skip, and it matters most, because a decision recorded with its reasoning intact can be revisited the moment the constraint changes, while one recorded only as a preference has nothing in it for a future reader to check against a changed world. The difference is not politeness; it is whether the decision survives the person who made it leaving the room.

The arc, closed

Seven stages, and each left you able to do something this lesson can only name. Stage 1, the JavaScript underneath, left you able to predict what a program does with no types involved. Stage 2, types over values, left you able to read and write the type vocabulary, annotating only where inference cannot reach. Stage 3, strictness and the compiler, left you able to configure the compiler with intent, naming what each flag buys. Stage 4, modelling, left you able to model a domain so illegal states are unrepresentable, with the compiler doing the proving. Stage 5, the runtime boundary, left you able to defend every boundary a value crosses, since lesson 29 already told you nothing survives to be there when the crossing happens. Stage 6, type-level tools, left you able to compute a type when a caller benefits and stop when they do not. Stage 7, judgment, left you able to publish, review and judge, this lesson's job being only to say what the other six already built up to. The mission set out at lesson 1 was to become the engineer trusted to own a TypeScript codebase on a team, and the standard was never a score, it was being the person a team hands a pull request to and trusts to say why a call was made. That is met now, by the last 48 lessons rather than by this one saying so. There is no lesson 50. What comes next is not a lesson, it is a codebase, a pull request, and the habit of asking the three questions above until it stops feeling like an exercise.

Practice

  1. ▢ A function typed to return Config is just return JSON.parse(raw);, and a caller reads config.port straight off the result with no check in between. Which of the four boundaries does this fall into, and which lesson owns it?
Check

The first boundary: it cannot check a value that arrives at run time. JSON.parse returns any regardless of the function's declared return type, so Config is asserted rather than earned, and lesson 30 owns the fix: type the boundary unknown and narrow.

  1. ▢ A recursive conditional type ships with no comment, defended by a tsc --diagnostics run showing a modest instantiation count and a check time under a second. Is that defence enough, and if not, what is missing?
Check

Not enough. The numbers show the type is cheap to check, a real question, but say nothing about whether a colleague can read the error it produces or maintain it in a year, the question lesson 42 keeps separate on purpose. What is missing is the one-minute readability test: cover the definition, look only at a call site, and try to say what it evaluates to.

  1. ▢ type Upload = { uploading: boolean; result?: string; error?: string };, and the domain only produces three states: uploading, succeeded with a result, or failed with a message. Which recurring decision does redesigning this type answer, and what number justifies the redesign?
Check

The second decision: what does this type permit that the domain does not. Upload permits eight states, two times two times two, against the three the domain produces, a gap of five, exactly lesson 24's method: count presence and absence on each field before touching the shape.

  1. ▢ One design note reads "we chose unknown here because it felt safer"; another reads "we chose it because this payload arrives from a queue we do not control, and we would revisit it if we owned both ends". Which survives the author leaving the team, and why?
Check

The second. It names the constraint, an untrusted producer, and the fact that would change the call, owning both ends of the queue, so a future reader can check whether that fact still holds. The first is a preference with no constraint attached, nothing in it to test against a changed situation.

  1. ▢ A colleague asks why the compiler rejected { a: 1, b: 2 } assigned straight to a variable typed { a: number }, but accepted the same value once routed through a plain variable first. Which stage answers this, and what is that stage's one-clause description of what it leaves you able to do?
Check

Stage 2, types over values, specifically lesson 11's excess property checking: a fresh literal meeting a typed target triggers a check ordinary structural assignability does not run, and routing it through a variable removes the literal that check was looking for rather than fixing anything. Stage 2's clause is reading and writing the type vocabulary, annotating only where inference cannot reach, exactly that vocabulary read correctly.

Real-world reps

  • [ ] Pick one boundary, one modelling decision, or one signature in a codebase you own, and write down which of the three recurring questions decides it and what your answer actually is.
  • [ ] Find one decision in a pull request, a commit message, or a comment that reads as a preference rather than a constraint, and rewrite it in the constraint, cost, and would-change-my-mind shape from this lesson.
  • [ ] Tomorrow: the next time someone asks why a piece of code is typed the way it is, answer out loud in that same shape before you open the file, and notice how much of the reasoning you actually remember without looking.

Going further

  • TypeScript Design Goals: the full numbered goals and non-goals list this arc has quoted from since lesson 11
  • Effective TypeScript: the source lesson 24 drew its counting method from, worth rereading now that the method has a home in a larger argument
  • Resources

Not landing? Reread the primary source at the top, since this lesson compresses it and compression is where understanding leaks. Check the glossary for any term that felt slippery.

If the lesson itself is unclear rather than the material, that is a defect: open an issue.

Table of contents