AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Blog
  3. 04 — Type Assertions: When You Know More Than the Compiler

04 — Type Assertions: When You Know More Than the Compiler

August 13, 20267 min read
Download as Markdown

"Overriding the compiler" was how I lumped assertions together, which made every misuse feel like a badge of expertise. Writing it down separated them: assertions are compile-time instructions to treat a value as a specific type, they perform no runtime conversion, and each flavor exists for a distinct situation. [1] Misusing them is how I used to silence real errors; using them deliberately is how I bridge gaps the compiler can't close on its own.

The unifying idea: none of these keywords do anything at runtime. They are notes to tsc. The shipped JavaScript is identical whether I write value as string or just value. That's why they're sharp tools — I'm taking responsibility for a type the compiler couldn't verify, and if I'm wrong, the program fails at runtime with no safety net [1].

as: the classic assertion

The as keyword tells the compiler to treat a value as a more specific (or different) type than it inferred [1]:

const el = document.getElementById("app") as HTMLAnchorElement;

getElementById returns HTMLElement | null — the compiler can't know it's specifically an anchor. I do (because the markup says so), so I assert. The honest framing is "I have information the compiler doesn't." The dishonest framing, which I try to avoid, is "make this error go away." A good as bridges a real gap; a bad as hides a real mismatch.

There's a guardrail: TypeScript only allows assertions between types where one is a subtype of the other (or through unknown). I can't write 42 as string — the compiler refuses, because number and string overlap. To force it I'd have to go through unknown, and needing to do that is usually a sign I'm fighting the types rather than working with them.

as const: locking down to literal types

as const tells the compiler to infer the narrowest possible type — literal types instead of widened ones [2]:

const status = "active";           // type: string (widened)
const status2 = "active" as const; // type: "active" (literal)

Without as const, a string literal widens to string. With it, the type is the exact literal "active". This unlocks two patterns: arrays of known values that act like enums, and objects whose properties become readonly literal types. It's the tool for when I want the compiler to treat my data as a fixed set of possibilities rather than a general type.

The non-null assertion !

The non-null assertion operator (!) tells the compiler to strip null and undefined from a type because I'm certain the value is present [3]:

const value = maybeNull!;
// maybeNull: string | null → value: string

It's a focused assertion: remove absence, trust me. Useful for things like document.getElementById where I've already checked the element exists, or for Map lookups where I know the key is set. The risk is identical to as — if I'm wrong, the runtime sees null and throws. I treat ! as a last resort; an explicit if (x !== null) check is safer because it survives refactors and reads clearly.

satisfies: validate without widening

The satisfies keyword (TypeScript 4.9) checks that a value conforms to a type without changing the value's inferred type [4]. This is the assertion flavor I underused for years:

const config = { port: 3000 } satisfies Config;
// config.port is still inferred as the literal 3000,
// not widened to number, AND it's validated against Config

The contrast with as is the point. as Config would replace the inferred type with Config, throwing away the narrower information. satisfies Config keeps the narrow type and adds a check that it matches Config. If it doesn't match, the compiler errors. This is the safe way to "assert" a shape — I get validation without losing precision, and I can't accidentally widen a type to something looser than what I wrote.

any and unknown: the two escape hatches

Two types let me step outside the type system entirely, and they sit at opposite ends of the safety spectrum.

any disables type checking for a value [5]. I can do anything to it, assign it anywhere, and the compiler stays silent. It's the "I give up" type — sometimes necessary for untyped third-party code or rapid prototyping, but every any is a hole where errors hide. The codebase rule I follow: any is a deliberate, commented choice, never a default.

unknown is the type-safe counterpart [6]. It represents "a value of unknown type," but unlike any, the compiler forces me to narrow it before I can use it — with a typeof check, an instanceof, or a type assertion. unknown is what any should have been: it acknowledges uncertainty without surrendering safety. For parsing external data (JSON, API responses), unknown plus narrowing is the correct pattern.

any checking OFF do anything, no errors unsafe escape hatch unknown checking ON must narrow before use safe escape hatch string | number fully checked specific, verified types the normal case safety increases →

How I use this

My hierarchy of preferences, from most to least used: satisfies when I want to validate a shape without losing the narrow inferred type; as const when I want literal types for fixed sets of values; explicit null checks over ! whenever the check reads cleanly; as only when I genuinely know more than the compiler (DOM lookups, parsed JSON I've validated); unknown over any for any external or untyped data; and any as a last resort, ideally flagged. The discipline that matters most is treating every assertion as a place where I am responsible for correctness instead of the compiler — so each one earns its place.

References

[1] Microsoft, "Type assertions," TypeScript Handbook, 2024. [Online]. Available: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions

[2] Microsoft, "const assertions," TypeScript 3.4 Release Notes, 2019. [Online]. Available: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions

[3] Microsoft, "Non-null assertion operator," TypeScript 2.0 Release Notes, 2016. [Online]. Available: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator

[4] Microsoft, "The satisfies operator," TypeScript 4.9 Release Notes, 2022. [Online]. Available: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator

[5] Microsoft, "any," TypeScript Handbook, 2024. [Online]. Available: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any

[6] Microsoft, "New unknown top type," TypeScript 3.0 Release Notes, 2018. [Online]. Available: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type

Knowledge check · Question 1 of 5

What does `value as string` do at runtime?

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!