Three different questions about the same program
Analysis of an excerpt starts by separating three questions: does the checker accept the program, does the tool produce an artifact, and does the received value satisfy the actual contract? A positive answer to the first does not automatically answer the others. A number annotation can detect incompatible uses known during checking, but it does not add a guard to every external caller.
The path adopts TypeScript 7.0 and explicitly states strict: true. It does not depend on compiler defaults. JavaScript still defines value behavior; React and Angular belong to their own paths. All assessments use choices about code and scenarios without requiring execution.
Inference retains a contract
In let attempts = 4, the initializer provides enough information to infer number. Absence of a written annotation does not imply any. Assigning "4" later contradicts that contract; the checker does not introduce conversion to make the assignment valid.
A const binding must also be distinguished from properties of its referenced object. const request = { mode: "read" } permits the property to receive other text. The property can therefore be inferred as string, wider than a literal union. An explicit type or deliberate literal preservation can express the contract; an assertion should not be added merely to silence a call.
unknown keeps the question open
Uninspected external data should retain uncertainty. unknown can receive any value but does not allow direct method or property use as though the shape were already established. A typeof condition, absence check or validator can supply missing information.
any permits many accesses without that evidence and can spread unchecked assumptions to consumers. Neither unknown nor any performs validation. The advantage of unknown is requiring the program to confront the boundary before use. An assertion can still bypass that requirement; review must examine the data path rather than only the type name.
Assertions do not transform values
The expression input as number asserts a static view. It does not call Number, check digits or choose an allowed range. If input contains text, the value remains text after the assertion is erased from emitted JavaScript. A double assertion supplies no additional evidence about the data either.
Postfix ! removes null and undefined from the static view at that use but does not invent a missing field. There must be a reason why absence is impossible. When real data can contradict the assumption, an explicit rejection, absence or recovery decision is more informative than hiding the diagnostic.
Absence needs its own policy
With strictNullChecks enabled, string | null describes two possibilities requiring handling before a string method call. An early return when the value is null permits string use on remaining paths. The same intent can be expressed with a conditional operator handling each possibility.
A truthiness condition is not automatically equivalent. If the empty string is valid, if (name) excludes it from the truthy branch. Domain policy must decide whether empty, absent and invalid mean different things. Changing configuration to remove the diagnostic changes none of those values and does not fix the policy.
Guided reading of a boundary
In the example, raw is unknown because the function does not assume correct input origin. It first requires nonempty text containing only ASCII digits. Only then does it convert and confirm an integer within range. Acceptance allows leading zeros: "007" represents quantity seven in this contract, not an identifier whose spelling must be preserved.
Predict decisions for "0", 0, "2.5", "501" and "007" without executing code. If the supplier starts sending numbers directly, the contract and first check must change together. Replacing these checks with as number would remove the validation on which the function depends.
Delivery evidence without confusing stages
A noEmit check can be a useful control before delivery, but it does not produce files another step may require. Identify who transforms TypeScript into JavaScript, which artifacts enter publication and what configuration was used. A report without diagnostics describes only the scope actually checked.
In this lesson’s cases, a decision starts with data and ends with an explicit consumer contract. Review should state which assumption was established and which remains external: input format, transformation, range, structure or artifact existence. Complete application behavior cannot be inferred from an annotation or a compilation without errors.
// TypeScript 7.0; strict: true; ES2022 library.
type QuantityResult =
| { ok: true; value: number }
| { ok: false; reason: "format" | "range" };
function readQuantity(raw: unknown): QuantityResult {
if (typeof raw !== "string" || raw.length === 0 || /[^0-9]/.test(raw)) {
return { ok: false, reason: "format" };
}
const value = Number(raw);
if (!Number.isInteger(value) || value < 0 || value > 500) {
return { ok: false, reason: "range" };
}
return { ok: true, value };
}Read the excerpt with strict: true. Text "0" is accepted; Number 0 is rejected by the agreed format; "501" exceeds the range. The function changes no external state, and no assertion replaces its checks.
Common pitfalls
Treating as number as conversion; trusting ! to populate fields; using any to hide incomplete contracts; confusing noEmit with a ready delivery.
Related topics: Unions, narrowing and state models · External data and error boundaries · Configuration, delivery and migration
A type describes what the program assumes. The boundary must check that received data supports that assumption.
Reference: TypeScript Handbook: The Basics · TypeScript 7.0; explicit strict and host configuration