Jiaxi Liu (Jesse)

Master’s Graduate

Software Engineer | Scalable APIs · Web Scraping · Data Integration · Code Quality & Refactoring

Back to Blog

TypeScript Control Flow, Record Dictionaries, and Array Iteration: for, for...of, for...in, forEach, and map

This note answers a practical question: when working with objects, arrays, dictionaries, and loops, which syntax should you use and why?

?? vs ||

?? uses the fallback only when the value is null or undefined.

const value = input ?? "default";

|| uses the fallback for any falsy value, including false, 0, "", NaN, null, and undefined.

const page = query.page || 1;

If 0 is a valid value, avoid ||.

const discount = userDiscount ?? 0;

Record Dictionaries

Record<K, V> describes a key-value map.

const userAges: Record<string, number> = {
  Alice: 25,
  Bob: 30,
};

If keys are a fixed set, use a union type.

type Role = "admin" | "editor" | "viewer";
 
const permissions: Record<Role, string[]> = {
  admin: ["read", "write", "delete"],
  editor: ["read", "write"],
  viewer: ["read"],
};

This is safer than Record<string, string[]> because missing roles become compile errors.

Index Signatures

Index signatures are useful when keys are fully dynamic.

interface ScoreMap {
  [name: string]: number;
}
 
const scores: ScoreMap = {};
scores.Alice = 95;

The tradeoff is that any string key is allowed.

Choosing a Loop

Use for when you need an index, early exit, or precise loop control.

for (let i = 0; i < items.length; i++) {
  if (items[i] === target) break;
}

Use for...of when you want values from arrays, strings, Sets, or other iterables.

for (const fruit of fruits) {
  console.log(fruit);
}

Use for...in for object keys, but guard against inherited properties.

for (const key in person) {
  if (Object.prototype.hasOwnProperty.call(person, key)) {
    console.log(key, person[key as keyof typeof person]);
  }
}

forEach vs map

forEach is for side effects and does not return a new array.

numbers.forEach((num) => {
  console.log(num);
});

map transforms an array and returns a new one.

const doubled = numbers.map((num) => num * 2);

Simple rules:

  • Need a new array: map
  • Need filtering: filter
  • Need accumulation: reduce
  • Logging or side effects: forEach
  • Need break or continue: for...of or for

Deeper Notes

When reviewing this topic, do not memorize names only. Focus on nullish coalescing, Record, index signatures, loop selection, array transformations, and side effects. If this stays at the definition level, it becomes hard to explain in interviews or apply in projects. A stronger way to study it is to place it in a concrete scenario: who calls it, where the input comes from, what happens on failure, and whether data or state can be processed twice.

  • TypeScript moves uncertainty from runtime to development time, but it does not replace runtime validation.
  • Type design should model business constraints first instead of showing off complex generics.
  • For third-party input, JSON, forms, and API responses, combine types with narrowing or schema validation.

In a real project, use it as a decision framework: identify inputs, constraints, failure modes, and observability before choosing a specific tool or pattern. If a solution looks simple, keep asking whether it still works when scale grows, permissions change, recovery matters, and more people collaborate on it.

Practical Checklist

  • Identify where this concept sits in the system: development-time constraint, runtime behavior, infrastructure capability, or collaboration workflow.
  • Write one minimal working example and one failure example; only knowing the happy path is usually not enough.
  • Record common misuses: edge cases, permission assumptions, performance assumptions, sync/async differences, or environment differences.
  • Connect the concept to a project experience so that an interview answer can be grounded in real tradeoffs.
  • End with one sentence about tradeoff: what it gives up and what it buys.

Self-Check Questions

  1. What core problem does this topic solve?
  2. What alternatives exist, and what are their costs?
  3. Where are the most likely edge cases?
  4. How would code, tests, or monitoring prove that it is reliable?

Applied Scenario

A practical scenario is a project with shared frontend/backend types. An API returns JSON; the frontend validates it with a schema, then passes the validated data into TypeScript types. Types improve editor feedback and compile-time checking, but they cannot guarantee that network data is valid. Good type design expresses business constraints such as roles, states, permissions, request parameters, and response shapes instead of turning everything into string or any.

Common Pitfalls:

  • Treating TypeScript types as runtime validation.
  • Using any for convenience and pushing errors into production.
  • Making generics so complex that callers cannot understand them.