# TypeScript Confidence Lab

## 1. Catch a mismatch before running

TypeScript checks relationships between values before the program runs. Here, a numeric quantity is assigned text. The compiler points to the conflict so you can fix the intended representation. A successful type check is useful evidence about your declared contracts, not proof that the program has no bugs.

**Try it:** Change the quoted quantity to a number, then compare diagnostics and emitted JavaScript.

```ts
let quantity: number = "3";
const total = quantity * 12;
export { total };
```

**Check:** What does type checking primarily examine?

**Answer:** Compatibility with declared and inferred types

## 2. Inference does useful work

You do not have to label every variable. TypeScript uses initializers and surrounding context to infer many types. An annotation can document an important boundary, while inference keeps local code readable. Inspect the inferred types in this lab before adding an annotation just to repeat what the initializer already says.

**Try it:** Check this example, then assign a string to score and check again.

```ts
let score = 10;
const label = "Points";
const doubled = score * 2;
export { score, label, doubled };
```

**Check:** If let score = 10, score is normally inferred as…

**Answer:** number

## 3. Primitive values

Use string, number and boolean for ordinary text, numeric and true/false values. A number type includes both fractional and whole numbers; it does not enforce an integer or a positive amount. Those domain restrictions need additional runtime logic. Types describe useful categories, but not every business rule is encoded automatically.

**Try it:** Replace price with a negative number. The type still checks; explain why a checkout may reject it at runtime.

```ts
const name: string = "Notebook";
const price: number = 12.5;
const available: boolean = true;
export { name, price, available };
```

**Check:** Does number require a positive integer?

**Answer:** No

## 4. Typed arrays

An array type describes the allowed element values. number[] and Array<number> express the same basic element constraint. The array can still be empty, and an arbitrary index may not exist. Turn on the indexed-access check to make that missing-element possibility more visible in later examples.

**Try it:** Try adding a string to the prices array and read the error.

```ts
const prices: number[] = [10, 20, 30];
const total = prices.reduce((sum, price) => sum + price, 0);
export { prices, total };
```

**Check:** What does number[] describe?

**Answer:** An array with numeric elements

## 5. Objects as contracts

An object type names the properties a value must provide and their types. This is especially useful at function and data boundaries. TypeScript uses structural compatibility: an appropriate shape matters more than the name of the object. A type declaration does not construct an object or validate a server response by itself.

**Try it:** Remove title from the task object. Then add it back with the wrong type.

```ts
type Task = { id: string; title: string; done: boolean };
const task: Task = { id: "t1", title: "Learn types", done: false };
export { task };
```

**Check:** Does declaring Task create a runtime object?

**Answer:** No

## 6. Function inputs and outputs

Annotate parameters when the caller supplies values and the function has a clear contract. A return annotation can help catch an accidental change in the result. The compiler checks the shape of the result, not whether a formula implements your intended pricing policy. Use runtime tests for that behavior.

**Try it:** Change the return expression to a string and check the return-type diagnostic.

```ts
function subtotal(price: number, quantity: number): number {
  return price * quantity;
}
const result = subtotal(12, 3);
export { subtotal, result };
```

**Check:** A return type of number guarantees…

**Answer:** A numeric result type, not the correct business formula

## 7. Optional properties

A question mark marks a property that may be absent. Reading it under strict null checking gives a type that includes undefined. Decide how your program should respond to absence: a fallback, a branch or an explicit error. Avoid hiding that decision with a non-null assertion unless you have a sound invariant.

**Try it:** Remove the nickname value and keep a usable display name with a fallback.

```ts
type Profile = { name: string; nickname?: string };
const profile: Profile = { name: "Ari" };
const display = profile.nickname ?? profile.name;
export { display };
```

**Check:** What can nickname be when absent?

**Answer:** undefined

## 8. Unions describe alternatives

A union allows a value to be one of several types. You can use an operation directly only when it is valid for every member that remains possible. A runtime check can narrow the possibilities. Model real alternatives rather than replacing a difficult type with any and losing the information.

**Try it:** Uncomment the unsafe uppercase call, read the diagnostic, then repair it using typeof.

```ts
function showId(id: string | number): string {
  // return id.toUpperCase();
  return typeof id === "string" ? id.toUpperCase() : String(id);
}
export { showId };
```

**Check:** Why narrow a union before a type-specific operation?

**Answer:** The operation may not exist on every member

## 9. Narrow with runtime evidence

typeof can separate strings, numbers and other primitive categories. The runtime check remains in emitted JavaScript, while the type annotations are removed. The checker uses the branch condition to decide which operations are valid inside each path. The narrowing explorer lets you see this branch reasoning with concrete values.

**Try it:** Switch the condition to typeof value === "number" and adapt both branches.

```ts
function describe(value: string | number): string {
  if (typeof value === "string") return value.trim();
  return value.toFixed(2);
}
export { describe };
```

**Check:** Which remains in emitted JavaScript?

**Answer:** The runtime typeof condition

## 10. Null is its own case

JavaScript reports typeof null as object. That makes a typeof-object check alone insufficient when accessing properties of unknown data. Check for null explicitly, and exclude arrays when you require a record. This is a good example of language behavior that types help you reason about but do not change.

**Try it:** Compare the null sample and object sample in the narrowing explorer.

```ts
function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
export { isRecord };
```

**Check:** What is typeof null?

**Answer:** "object"

## 11. Literal types constrain choices

A literal union can describe a small set of valid labels, such as draft or published. This catches spelling errors at checked call sites. It does not stop an unvalidated external string from arriving at runtime. Use a corresponding runtime check when the value crosses a trust boundary.

**Try it:** Add an invalid status to the example. Then extend the type deliberately if that state belongs in the model.

```ts
type Status = "draft" | "published";
function label(status: Status) { return status.toUpperCase(); }
const current: Status = "draft";
export { current, label };
```

**Check:** What does a literal union help catch?

**Answer:** Unsupported labels at checked call sites

## 12. Discriminated unions

Give each alternative a distinct literal field, such as kind. Checking that field narrows the corresponding payload. This models states like loading, success and error without allowing unrelated fields to drift into impossible combinations. Each branch should work with the fields that actually belong to its variant.

**Try it:** Change kind in the example and inspect which payload the branch permits.

```ts
type Result = { kind: "success"; value: number } | { kind: "error"; message: string };
function explain(result: Result): string {
  return result.kind === "success" ? String(result.value) : result.message;
}
export { explain };
```

**Check:** Which field distinguishes the variants here?

**Answer:** kind

## 13. Generics preserve relationships

A generic parameter can express a relationship between an input and an output without throwing away their specific type. An identity function returns the same type it receives. This differs from any, which often loses the very connection you want the checker to preserve. Prefer simple generics with a clear purpose.

**Try it:** Call identity with a string and then an object. Inspect each inferred result type.

```ts
function identity<T>(value: T): T { return value; }
const name = identity("Ari");
const point = identity({ x: 1, y: 2 });
export { identity, name, point };
```

**Check:** What relationship does identity<T>(value: T): T express?

**Answer:** Input and output share the same type parameter

## 14. Constrain generic inputs

A generic constraint describes what a reusable function needs from its input. If a function reads length, it can require a length property rather than claiming it accepts absolutely anything. The caller can keep a more specific type while still satisfying the operation’s minimum requirements.

**Try it:** Try passing a number to size, then a string or array.

```ts
function size<T extends { length: number }>(value: T): number {
  return value.length;
}
const count = size([1, 2, 3]);
export { size, count };
```

**Check:** Why constrain T with a length property?

**Answer:** The function reads length

## 15. Select properties with keyof

keyof describes the property names of a type. Combined with indexed access, it can link a requested key to the corresponding result type. This is useful for small reusable helpers, but more complex type machinery is not automatically better. Use it when it makes a real relationship clearer.

**Try it:** Change the requested key from title to done and inspect the result type.

```ts
function get<T, K extends keyof T>(object: T, key: K): T[K] { return object[key]; }
const task = { title: "Read", done: false };
const value = get(task, "title");
export { get, value };
```

**Check:** What does K extends keyof T restrict?

**Answer:** K to keys of T

## 16. Utility types reshape contracts

Pick, Omit and Partial derive related types from an existing shape. A patch type can make selected fields optional without duplicating the original declaration. These tools change compile-time descriptions; they do not remove object properties from runtime values or automatically apply a patch.

**Try it:** Inspect the patch type and try adding a property outside its allowed fields.

```ts
type Task = { id: string; title: string; done: boolean };
type TaskPatch = Partial<Pick<Task, "title" | "done">>;
const patch: TaskPatch = { done: true };
export { patch };
```

**Check:** Does Omit remove properties from an existing runtime object?

**Answer:** No

## 17. Readonly is a checked restriction

Readonly properties prevent direct assignment through that typed reference. They are useful for communicating intent and limiting accidental mutation in checked code. Readonly is not the same as freezing an object at runtime, and shallow readonly wrappers do not automatically make every nested object immutable.

**Try it:** Try changing the title directly. Then create a replacement object using spread.

```ts
type Task = { readonly id: string; readonly title: string };
const task: Task = { id: "t1", title: "Read" };
const updated = { ...task, title: "Practice" };
export { task, updated };
```

**Check:** Does readonly automatically call Object.freeze?

**Answer:** No

## 18. Array access can be missing

Reading an arbitrary array index can return undefined even when the element type is number. noUncheckedIndexedAccess makes this possibility explicit. A fallback or existence check may be needed before using the value. A tuple can describe a known fixed position, but ordinary arrays do not guarantee a particular index exists.

**Try it:** Enable the indexed-access option, then replace the unsafe multiplication with a checked fallback.

```ts
const values: number[] = [];
const first = values[0];
const doubled = first * 2;
export { first, doubled };
```

**Check:** What can an out-of-range array access return?

**Answer:** undefined

## 19. Unknown versus any

unknown requires you to establish what a value is before performing type-specific operations. any largely opts out of that checking. Unknown is a useful starting point for external data because it encourages an explicit validation step. Neither keyword itself inspects a network response at runtime.

**Try it:** Try calling trim directly on value, then restore the typeof check.

```ts
function clean(value: unknown): string {
  return typeof value === "string" ? value.trim() : "";
}
export { clean };
```

**Check:** Which type requires narrowing before type-specific use?

**Answer:** unknown

## 20. Assertions are not validation

An assertion tells the checker to treat a value as a type; it does not convert or inspect that value at runtime. A careless assertion can silence the diagnostic while leaving the actual problem untouched. Prefer checks at external boundaries, and reserve assertions for situations where you have evidence the checker cannot express.

**Try it:** Inspect the emitted JavaScript. Notice that the assertion disappears. Do not run the unsafe example as production code.

```ts
const external: unknown = { name: 123 };
const claimed = external as { name: string };
// This compiles, but would fail if executed:
const output = claimed.name.toUpperCase();
export { output };
```

**Check:** What runtime validation does an as assertion perform?

**Answer:** None

## 21. Validate JSON before using it

Parsing JSON verifies syntax, not the expected application shape. After parsing, check the object, required fields, value categories and relevant constraints. The API contract builder provides a working validator and exports its source so you can compare the static interface with the actual runtime checks.

**Try it:** Try a valid user, a missing field, null and a wrong field type in the contract builder.

```ts
type User = { id: number; name: string };
function isUser(value: unknown): value is User {
  if (typeof value !== "object" || value === null) return false;
  const record = value as Record<string, unknown>;
  return typeof record.id === "number" && typeof record.name === "string";
}
export { isUser };
```

**Check:** What does JSON.parse establish by itself?

**Answer:** Valid JSON syntax, not the application shape

## 22. Types are erased

The emitted JavaScript contains the operations that run. Interfaces, most annotations and type aliases do not remain as automatic validators. Compare the source with the output to understand which pieces are compile-time descriptions and which are runtime checks. This lab compiles code but never executes your editor text.

**Try it:** Compare the interface and typed function with their emitted JavaScript.

```ts
interface Point { x: number; y: number }
function distance(point: Point): number {
  return Math.hypot(point.x, point.y);
}
export { distance };
```

**Check:** Which part normally remains in emitted JavaScript?

**Answer:** Math.hypot(point.x, point.y)

## 23. Use strict checks deliberately

Strict checking turns on a group of stronger checks. Additional options such as indexed-access checking and exact optional properties address other tradeoffs. Understand what each option reveals instead of treating all diagnostics as noise. A project should keep its chosen configuration with its source so teammates see the same rules.

**Try it:** Compare strict on and off for the null example. Keep strict enabled while practicing repairs.

```ts
function upper(value: string | null): string {
  return value.toUpperCase();
}
export { upper };
```

**Check:** What should you do with a useful strict diagnostic?

**Answer:** Investigate the assumption it exposes

## 24. Ship types and behavioral tests

A clean compiler result checks the relationships that the type system can express. Runtime tests still matter for calculations, edge cases, user behavior and external systems. Export the source and matching options from this lab, then continue in a normal project with tests and dependency management. The two forms of evidence complement each other.

**Try it:** Export your source and tsconfig, and write down one behavior that still needs a runtime test.

```ts
function discount(price: number): number {
  return price * 2; // Type-correct, but is this the intended discount?
}
export { discount };
```

**Check:** Can a type-correct formula still implement the wrong business rule?

**Answer:** Yes

References: https://www.typescriptlang.org/docs/handbook/ and https://www.typescriptlang.org/tsconfig/
