learn
TypeScript

any, unknown, assertions and satisfies

Escape hatches, guards, the ! operator and why types are not validation.

any disables the checker, unknown forces you to convince it. Interviewers use this pair to separate people who type from people who silence — reach for unknown plus a guard, and say why out loud.

Real-life situation

Your app parses JSON from an API. The payload could be anything, so unknown is the honest label — then a guard proves it has a string name before you touch it. Typing the same payload any compiles silently and crashes loudly: count + 1 becomes "[object Object]1" with no warning anywhere.

Visual

Unknown versus any
BrowserAPIRealtimeCacheDatabaseShareddurableephemeral

Try it

Narrow unknown API data safely

Checked with TypeScript 6.0.3, strict mode

typescriptTab moves to the next control
function parsePayload(text: string): unknown {
return JSON.parse(text);
}
const raw = parsePayload('{"name":"sam"}');
if (typeof raw === "object" && raw !== null && "name" in raw) {
const record = raw as { name: unknown };
if (typeof record.name === "string") {
  console.log("hello " + record.name);
}
}
const direct: string = parsePayload('"sam"');
console.log("direct: " + direct);
const count: number = raw as number;
console.log(count + 1);
type LooseTheme = Record<string, string | number>;
const theme = { color: "blue", size: 12 } satisfies LooseTheme;
console.log(theme.color + " " + theme.size);
console.log(typeof raw);

Compiler not loaded. Check or Run loads it here (about 9 MB) — nothing downloads until you do.

Runner helpers

sleep(ms) waits; fakeFetch(url) returns deterministic sample data. TypeScript is stripped before running; imports and exports are not available in the sandbox.

Try changing raw as number to a guarded extraction — the error stays until the types line up, which is the point. Assertions (as) tell the checker what you claim to know; guards (typeof, in, custom is) prove it. satisfies checks a value against a type without widening it. The ! non-null assertion (user!.name) is a promise without proof — the exercise system rejects it, and so should you.

Types are never runtime validation: unknown only organizes your own doubt. Real payloads need real checks at the boundary (see typing an API call).

Will it compile? — decide first, then reveal.

check.ts
const loose: any = { name: "sam" };
const name: string = loose.name;
console.log(name);

Interview questions

What does any mean?basic

any opts out of checking — the value can be anything and every operation on it is allowed. It is an escape hatch for untyped boundaries and quick prototypes, but overuse spreads unchecked values through typed code. The compiler will not help you with an any, so errors resurface at runtime. Treat each any as tech debt: contain it at the boundary and convert to a real type or unknown plus narrowing as soon as you can.

Interviewers also ask

  • Why should you contain any at the boundary?
What does unknown mean?basic

unknown means a value of uncertain type that you must narrow before use — the type-safe counterpart to any. Assign anything to it, but the compiler forbids calling, indexing, or reading properties until you prove the shape with a guard. It suits parsed JSON, caught errors, and plugin inputs. Prefer unknown over any whenever you cannot name the type yet but still want every use site to verify before proceeding.

Interviewers also ask

  • What must you do before using an unknown value?
How do you narrow an unknown value safely?basic

You apply runtime checks — typeof, instanceof, in, or a custom guard — and the compiler shrinks unknown to the proven type in that branch. For example, if (typeof data === 'string') lets you call string methods inside. For objects, validate the needed properties before casting. Never jump straight to as without checking: narrowing keeps the runtime test and the static type in agreement.

Interviewers also ask

  • Why check before you cast?
How does the as keyword work?basic

value as User tells the compiler to treat the value as that type for the rest of the expression. It performs no runtime conversion or validation — it is purely your assertion, and the emitted code passes the value through unchanged. Use it after you have validated, or for cases the checker cannot see like DOM lookups. If the types do not overlap enough, the compiler rejects the assertion and asks you to justify it more explicitly.

Interviewers also ask

  • Does as convert or validate anything at runtime?
How is satisfies different from as?intermediate

satisfies checks that an expression matches a type while keeping its precise inferred shape, whereas as widens or overrides to the asserted type. A config object with satisfies Config still exposes its literal values for autocomplete, but a mistyped field errors immediately. Use satisfies when you want validation without losing narrowness — theme objects and route tables are classic cases. Reach for as only when you intentionally re-label a value.

Interviewers also ask

  • Why does satisfies preserve literals better than as?
What does the non-null assertion ! do?intermediate

Appending ! like user!.name tells the compiler a nullable value is definitely present, removing null and undefined from the type. It emits no runtime check — if you are wrong, the code still crashes. Use it sparingly for cases the checker cannot see, such as values set by a framework lifecycle. Prefer explicit guards or defaults wherever the absence is genuinely possible, since those handle the failure instead of asserting it away.

Interviewers also ask

  • What happens at runtime if a ! assertion is wrong?
What are assertion functions with asserts?intermediate

Functions declared asserts value is string throw on failure and narrow the argument in code that follows — no if needed at the call site. You centralize validation like assertIsUser(data) and every later line sees the proven type. They suit loaders and test setup where invalid data must halt execution. Keep each assertion focused on one shape so failures point clearly at what was missing.

Interviewers also ask

  • How does an assertion function differ from a type guard?
When is a double assertion like as unknown as T justified?intermediate

It forces a conversion between types that do not overlap enough for a single as, by routing through unknown first. It is occasionally needed for test doubles, deserialized payloads, or legacy interop. Treat it as a loud warning: you have bypassed the overlap check, so validate at runtime before trusting the result. If you reach for it often, the underlying types likely need remodeling rather than more forceful casting.

Interviewers also ask

  • What runtime step should follow a double assertion?

Key takeaways

  • unknown plus a guard; any only when you can defend it.
  • Assertions claim, guards prove — prefer proof at boundaries.
  • No annotation validates runtime data; check payloads for real.

References

On this page