Error handling: thrown errors, unknown, and boundary validation
Translate Python exception instincts into TypeScript's thrown-error model while learning where unknown and runtime validation belong.
by the end of this lesson you can
- →Starts from a runtime-safe input type such as unknown when appropriate
- →Narrows before property access
- →Keeps validation and thrown-error behavior easy to follow
Overview
Python developers understand exceptions already, but TypeScript adds an important split: the compiler can help shape what you believe about failures, while runtime validation is still required when untrusted data enters the system. Catch values and external inputs should be treated carefully rather than assumed safe.
In Python, you often
raise and catch exceptions directly, often trusting internal callers to pass data in shapes that mostly behave as expected.
In TypeScript, the common pattern is
to throw errors normally, treat caught values and external data defensively, and use validation at boundaries because types alone cannot inspect runtime input.
why this difference matters
This is where TypeScript teaches discipline. The compiler gives confidence inside trusted code, but API responses, JSON, and caught values still need careful narrowing or validation before use.
Python
try:
user = load_user(payload["id"])
except ValueError as error:
print(error)TypeScript
try {
const user = loadUser(payload.id);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
}
}Deeper comparison
Python version
def parse_age(data: dict[str, object]) -> int:
value = data["age"]
if not isinstance(value, int):
raise ValueError("invalid age")
return valueTypeScript version
function parseAge(data: unknown): number {
if (
typeof data !== "object" ||
data === null ||
!("age" in data) ||
typeof data.age !== "number"
) {
throw new Error("invalid age");
}
return data.age;
}Reflect
What does TypeScript make clearer about error and input handling that dynamic Python code can leave implicit until later?
what a strong answer notices
A strong answer mentions that external data is not automatically trustworthy, unknown forces explicit checks, and compile-time confidence does not remove runtime validation needs.
Rewrite
Rewrite this Python validation helper into TypeScript and keep the trust boundary explicit.
Rewrite this Python
def read_name(data: dict[str, object]) -> str:
value = data["name"]
if not isinstance(value, str):
raise ValueError("invalid name")
return value.strip()what good looks like
- Starts from a runtime-safe input type such as unknown when appropriate
- Narrows before property access
- Keeps validation and thrown-error behavior easy to follow
Practice
Design a TypeScript function that accepts JSON input, validates a user payload at the boundary, and returns a typed result object for trusted internal use.
success criteria
- Separates runtime validation from trusted typed code
- Uses unknown or equivalent boundary discipline intentionally
- Avoids pretending compile-time types can validate raw JSON by themselves
Common mistakes
- Assuming catch gives you a useful typed error automatically.
- Treating external JSON as if a TypeScript annotation had already validated it.
- Using broad assertions to silence the compiler instead of proving the boundary shape first.
takeaways
- ●This is where TypeScript teaches discipline. The compiler gives confidence inside trusted code, but API responses, JSON, and caught values still need careful narrowing or validation before use.
- ●A strong answer mentions that external data is not automatically trustworthy, unknown forces explicit checks, and compile-time confidence does not remove runtime validation needs.
- ●Separates runtime validation from trusted typed code