Imagine you have boxes in a warehouse. Some boxes say “books,” some say “tools,” and some are empty or mysterious. In TypeScript, types are like labels that help you sort and check boxes before shipping them.
The important part is that the label is checked before the package leaves the warehouse, not by opening every box in the real world later. That is why TypeScript can warn you about mistakes early, but it does not change what exists at runtime. It helps you catch problems while writing code, like a smart checklist.
What TypeScript Fundamentals Mean
TypeScript adds compile-time type checking to JavaScript. That means it looks at your code and your annotations to catch mistakes before the program runs, but the types themselves disappear when the code is executed.
This matters because it gives you stronger guarantees without changing JavaScript’s runtime behavior. The core ideas here are:
Basic types like string, number, boolean, arrays, and object shapes.
Inference, where TypeScript figures out a type for you.
The boundary between compile time and runtime, where TypeScript can warn you, but JavaScript still decides what actually happens.
Special types: any, unknown, and never.
Structural typing, where shape matters more than declared names.
Basic Types and Annotations
You can annotate values with basic types to tell TypeScript what you expect. This is useful when the type is not obvious, or when you want to communicate intent to other engineers.
Common examples include:
string for text
number for numeric values
boolean for true/false
arrays like string[]
object shapes like { name: string; age: number }
Annotations do not create new runtime behavior. They only help TypeScript check whether the values you use match what you said you wanted.
let name: string = "Ava";
let count: number = 3;
let active: boolean = true;
let tags: string[] = ["ts", "js"];
let user: { name: string; age: number } = { name: "Ava", age: 30 };
Inference: TypeScript Guesses for You
Type inference means TypeScript can often figure out a type from the value you assign. This reduces noise and still keeps safety, so you do not have to annotate everything manually.
Inference matters most when the initializer makes the type obvious:
let x = 42 becomes number
const role = "admin" becomes the literal type "admin"
function return values are often inferred too
A good rule is: annotate when the type is unclear or part of your public API, and let inference handle the rest when the value already makes the type obvious.
let age = 42; // inferred as number
const role = "admin"; // inferred as "admin"
function double(n: number) {
return n * 2; // inferred return type: number
}
Compile-Time vs Runtime Boundary
TypeScript checks types before your code runs. At runtime, the type annotations are gone, and JavaScript only sees values.
That means TypeScript can prevent many mistakes, but it cannot protect you from data coming from the outside world unless you check it at runtime. For example, JSON from an API might claim to be a User, but TypeScript cannot trust that without validation.
This boundary is the key interview idea: types are a static guarantee, not a runtime guarantee. When values come from unknown sources, you must still check them with real JavaScript code.
type User = { name: string };
const raw = JSON.parse('{"name":123}');
// TypeScript does not know if raw is really a User.
console.log(raw.name); // runtime may still fail later
any, unknown, and never
These three types are easy to confuse, but they play very different roles.
any is the unsafe escape hatch. It turns off type checking for that value, so you can do almost anything and TypeScript will not complain.
unknown is the safe version for values you do not yet understand. You can receive it, store it, and narrow it before using it.
never means impossible. It represents a value that should not exist, often used for functions that cannot return or for exhaustive checks.
Interviewers like to ask why unknown is better than any: because it forces you to prove safety before using the value.
let a: any = 123;
a.trim(); // allowed, but unsafe
let u: unknown = 123;
// u.trim(); // error until narrowed
function fail(msg: string): never {
throw new Error(msg);
}
Type Assertions and Structural Typing
A type assertion tells TypeScript, “trust me, I know what this is.” It does not convert the value or check it at runtime, so it can hide bugs if you assert incorrectly.
Structural typing means TypeScript cares about the shape of a value, not the declared name of its type. If two objects have the same required properties, they are compatible even if they were named differently.
This is why TypeScript often feels like duck typing: if it looks like a duck and quacks like a duck, TypeScript treats it like a duck. The powerful part is compatibility by shape; the dangerous part is assuming shape without checking runtime data.
type Point = { x: number; y: number };
const p = { x: 1, y: 2, label: "hi" };
const pt: Point = p; // ok: shape matches
const raw = "hello" as string; // assertion, no runtime check
Putting It All Together
Suppose you receive data from an API and want to use it as a User. TypeScript can help you model the shape, but you still need to be careful about the runtime source.
Here, inference handles obvious values, structural typing accepts a compatible object, and unknown forces a safe check before use. If you instead used any, the code would compile even when the data is wrong, which is exactly the trap TypeScript is trying to save you from.
The key interview takeaway is that TypeScript gives you a safer development experience, but only after you respect the boundary between compile-time types and runtime values.
type User = { name: string; age: number };
const candidate: unknown = { name: "Mia", age: 28 };
if (
typeof candidate === "object" &&
candidate !== null &&
"name" in candidate &&
"age" in candidate
) {
const user = candidate as User;
console.log(user.name.toUpperCase());
}
Common Traps Interviewers Set
Watch for these classic mistakes:
Confusing TypeScript checks with runtime validation. A type annotation does not sanitize API data.
Using any as a shortcut. It hides bugs and defeats the point of the type system.
Treating unknown like any. You must narrow it before use.
Forgetting that never means impossible, not “an empty object” or “nothing I care about.”
Assuming type names matter more than shape. With structural typing, compatibility is based on properties, not on class or interface names.
Overusing type assertions to silence errors instead of fixing the real type mismatch.
In interviews, the best answer usually explains both the convenience and the risk of each feature, especially where the static system stops and runtime reality begins.