a. algoviz / study sheet

TS DATA MANIPULATION / IMPLEMENTATION ORDER

Build it in
the right order.

Your TypeScript study sheet, arranged by what you need first. Learn the small moves, then combine them into nested updates and joins.

35 original examples10 stagesPractice order + coding steps

THE REPEATABLE ORDER

Shape → output → operation → callback → check

Read the input shape. Name the result type. Pick the method. Write its callback. Verify the values, missing-data behavior, and what stayed unchanged.

Choose by the result you need

One value per itemmap
A subset of itemsfilter
The first matchfind
A yes / no answersome / every
A total or groupsreduce
Zero or more per itemflatMap
The shared data: users, settings, company View TypeScript +

Each copy button includes this data, so the example runs on its own.

sample-data.ts
interface User {
  id: number;
  name: string;
  age: number;
  active: boolean;
}
const users: User[] = [
  {id: 1, name: "Ana", age: 34, active: true},
  {id: 2, name: "Ben", age: 25, active: false},
  {id: 3, name: "Cara", age: 41, active: true},
  {id: 4, name: "Dan", age: 25, active: false},
];
const settings: Record<string, string | number | undefined> = {
  theme: "dark", fontSize: 14, language: "en", timezone: undefined,
};
interface Company {
  name: string;
  address: {city: string; zip: string};
  teams: {id: number; name: string; memberIds: number[]}[];
}
const company: Company = {
  name: "Acme",
  address: {city: "Boston", zip: "02101"},
  teams: [
    {id: 1, name: "Frontend", memberIds: [1, 2]},
    {id: 2, name: "Backend", memberIds: [3, 4]},
  ],
};
10 / 10 stages
01

FOUNDATIONS

Read the shape

Object.keys / values / entries · ?. · ??

Start by locating the data and deciding what can be missing. Every later transformation depends on this.

Write it in this order

  1. Identify whether the input is an array, object, or nested combination.
  2. Read the needed property or turn an object into keys, values, or pairs.
  3. Guard a possibly missing array item with ?. and choose a fallback with ?? only if needed.
Remember
Object.entries gives [key, value] pairs. Optional chaining handles null or undefined, not every possible runtime error.
Watch for
An object containing timezone: undefined still has that key. Object.keys checks own enumerable string keys; an “empty” result does not describe a Map or Set.
TRY FROM MEMORY

Read team index 9 safely and test whether settings is empty.

Check: undefined; false

TypeScript implementation Open code +
Includes sample data when copied
const keys = Object.keys(settings);
const values = Object.values(settings);
const pairs = Object.entries(settings);
const isEmpty = Object.keys(settings).length === 0;

const city = company.address.city;
const secondTeamName = company.teams[1]?.name;
const tenthTeamName = company.teams[9]?.name;
02

FOUNDATIONS

Write the loop first

for…of · entries() · indexed for · break · for…in

A loop makes the accumulator, condition, and stopping rule explicit. Array methods are easier once you can write their loop version.

Write it in this order

  1. Initialize the result with its type: an array, a counter, or Item | undefined.
  2. Iterate the items with for…of; use entries() when you also need the index.
  3. Update the result, and break as soon as a first-match task is solved.
Remember
for…of gives values; for…in gives string keys and may include inherited enumerable properties. Object.entries is the usual choice for own object pairs.
Watch for
Do not use for…in to get array items. An indexed lookup can be undefined; guard it. A first-match result must allow “not found.”
TRY FROM MEMORY

Find the oldest user and the first inactive user with loops.

Check: Cara; Ben

TypeScript implementation Open code +
Includes sample data when copied
const loudNames: string[] = [];
for (const user of users) {
  loudNames.push(user.name.toUpperCase());
}

let oldestLoop: User | undefined;
for (let i = 0; i < users.length; i++) {
  const user = users[i];
  if (user && (!oldestLoop || user.age > oldestLoop.age)) {
    oldestLoop = user;
  }
}

let firstInactive: User | undefined;
for (const user of users) {
  if (!user.active) {
    firstInactive = user;
    break;
  }
}

for (const [index, user] of users.entries()) {
  console.log(index, user.name);
}
for (const [key, value] of Object.entries(settings)) {
  if (value !== undefined) console.log(key, value);
}
const settingLines: string[] = [];
for (const [key, value] of Object.entries(settings)) {
  settingLines.push(key + ": " + String(value));
}
const keyList: string[] = [];
for (const key in settings) keyList.push(key);
03

FOUNDATIONS

Choose the output, then the method

map · filter · find · some · every

Choose by the return shape: transformed array, retained items, one item, or a boolean.

Write it in this order

  1. Write down the output type before choosing a method.
  2. Use map for one output per item, filter for a subset, find for one match, or some/every for a boolean.
  3. Write the smallest callback that returns exactly what that method needs.
Remember
map returns a new value; filter/find/some/every return a test from their callback. filter preserves the original item references.
Watch for
An arrow callback with { braces } needs an explicit return. On an empty array: find → undefined, some → false, every → true.
TRY FROM MEMORY

Get the names, active users, first user under 30, and whether everyone is active.

Check: [Ana, Ben, Cara, Dan]; [Ana, Cara]; Ben; false

TypeScript implementation Open code +
Includes sample data when copied
const names = users.map(u => u.name);
const activeUsers = users.filter(u => u.active);
const firstYoung = users.find(u => u.age < 30);
const anyActive = users.some(u => u.active);
const allActive = users.every(u => u.active);

// The same filtering move, written as a loop.
const inactiveLoop: User[] = [];
for (const user of users) {
  if (!user.active) inactiveLoop.push(user);
}
04

SINGLE-LEVEL OPERATIONS

Accumulate, then group

reduce · typed accumulator · bucket initialization

Grouping is the same accumulation pattern as a sum, with a collection as the result.

Write it in this order

  1. Choose the accumulator type and give reduce an initial value.
  2. For a total, add one value; for a group, calculate its key and obtain or create its bucket.
  3. Update the accumulator and return it on every callback call.
Remember
Use 0 for sums and {} or a Map for groups. A local accumulator created for this result can be mutated without mutating the input.
Watch for
reduce without an initial value throws on empty input. Object keys are strings at runtime, even for numeric ages. Prefer Map for arbitrary keys such as user-supplied names.
TRY FROM MEMORY

Calculate total age, then group users by age.

Check: 125; age 25 contains Ben and Dan

TypeScript implementation Open code +
Includes sample data when copied
const totalAge = users.reduce((sum, u) => sum + u.age, 0);

const byAge = users.reduce<Record<number, User[]>>((acc, u) => {
  const bucket = acc[u.age] ?? [];
  bucket.push(u);
  acc[u.age] = bucket;
  return acc;
}, {});
05

SINGLE-LEVEL OPERATIONS

Order and deduplicate

sort · numeric comparator · localeCompare · Set

These often finish a transformation. First decide whether you are sorting objects or derived values.

Write it in this order

  1. Copy the input array before sort if you need to preserve it.
  2. Choose the comparison: numeric subtraction or localeCompare for strings.
  3. For primitive uniqueness, extract the values, create a Set, then spread it back to an array.
Remember
A comparator returns negative, zero, or positive. Set keeps the first insertion order.
Watch for
sort mutates. Default sorting compares strings. Set deduplicates objects by reference, so two separate {id: 1} objects remain two entries.
TRY FROM MEMORY

Sort users by age, then get unique ages in first-seen order.

Check: [Ben, Dan, Ana, Cara]; [34, 25, 41]

TypeScript implementation Open code +
Includes sample data when copied
const byAgeAsc = [...users].sort((a, b) => a.age - b.age);
const byName = [...users].sort((a, b) => a.name.localeCompare(b.name));
const ages = users.map(u => u.age);
const uniqueAges = [...new Set(ages)];
06

SINGLE-LEVEL OPERATIONS

Implement the array update trio

add: spread · remove: filter · update: map + spread

These three moves are the foundation of immutable UI state updates.

Write it in this order

  1. Identify the operation and the stable id of the target item.
  2. Add with [...items, newItem], remove with filter, or update with map.
  3. For an update, spread-patch only the matching item and return every other item unchanged.
Remember
A new array does not require new copies of every item. Keep unchanged references; create a new object only for the edited item.
Watch for
Mutating u.active inside map still changes the original object. Ids should be unique; otherwise this map updates every match.
TRY FROM MEMORY

Add Eve, remove Ben, and activate Ben as three independent results.

Check: 5 items; ids [1, 3, 4]; Ben active in the new result only

TypeScript implementation Open code +
Includes sample data when copied
const withEve = [
  ...users,
  {id: 5, name: "Eve", age: 30, active: true},
];
const withoutBen = users.filter(u => u.id !== 2);
const benActivated = users.map(u =>
  u.id === 2 ? {...u, active: true} : u
);
07

SINGLE-LEVEL OPERATIONS

Rebuild objects and make a lookup

spread · rest · entries → map/filter → fromEntries

Use the same transform/filter ideas on key-value pairs. Build the id lookup now because the later join depends on it.

Write it in this order

  1. For a patch, spread the object then write the override; for removal, destructure the unwanted key and keep ...rest.
  2. For a transformation, get entries, map or filter the pairs, then call Object.fromEntries.
  3. To build an index, map each user to [id, user], then call Object.fromEntries.
Remember
The pair is the unit of object transformation. Keep the key when changing a value. Explicitly test value !== undefined to preserve 0, false, and empty strings.
Watch for
Later properties win. Setting a value to undefined does not remove its key. Duplicate ids in fromEntries keep the last value; a missing lookup still needs a guard.
TRY FROM MEMORY

Set fontSize to 18, remove timezone, drop undefined values, and index users by id.

Check: 18; timezone key absent; defined settings; usersById[3] is Cara

TypeScript implementation Open code +
Includes sample data when copied
const bigger = {...settings, fontSize: 18};
const {timezone, ...withoutTimezone} = settings;

const stringified = Object.fromEntries(
  Object.entries(settings).map(([key, value]) => [key, String(value)])
);
const defined = Object.fromEntries(
  Object.entries(settings).filter(([, value]) => value !== undefined)
);
const usersById = Object.fromEntries(users.map(u => [u.id, u]));
const cara = usersById[3];
08

COMPOSE THE MOVES

Copy the path to a nested change

nested spread · shallow copy · structuredClone

A nested update is several flat updates composed along one path.

Write it in this order

  1. Locate the changed leaf, such as company.address.city.
  2. Write the replacement from the inside out: copy address and override city, then copy company and replace address.
  3. Check that changed ancestors are new references and untouched branches can still be shared.
Remember
Spread copies one level. For cloneable data, structuredClone creates a deep copy that you can then mutate independently.
Watch for
A top-level spread still shares address and teams. structuredClone copies the whole graph, loses useful sharing, and cannot clone values such as functions or DOM nodes.
TRY FROM MEMORY

Move the company to Denver without changing the original Boston address.

Check: moved !== company; moved.address !== company.address; moved.teams === company.teams

TypeScript implementation Open code +
Includes sample data when copied
const moved = {
  ...company,
  address: {...company.address, city: "Denver"},
};

const shallow = {...company};
const sharesAddress = shallow.address === company.address; // true

const clone = structuredClone(company);
clone.address.city = "Austin";
09

COMPOSE THE MOVES

Compose the trio inside nested arrays

outer spread → map target → inner spread/filter

No new primitive is needed: reuse the array trio inside the copied object path.

Write it in this order

  1. Write the outer {...company, teams: …} shell, then map the teams to locate the target id.
  2. Return nonmatching teams unchanged. For the match, spread the team and replace the changed property.
  3. For memberIds, add with a spread or remove with filter; then verify both values and reference identity.
Remember
For company → teams → team → memberIds, copy every container on that path. An unchanged team should keep its reference.
Watch for
Copying the team but pushing into its existing memberIds still mutates the original. Specify what should happen when a target id is missing or a member is already present.
TRY FROM MEMORY

Rename Backend to Platform; add member 5 to Frontend; remove member 3 from Backend.

Check: Platform; [1, 2, 5]; [4] — each in its own result

TypeScript implementation Open code +
Includes sample data when copied
const renamed = {
  ...company,
  teams: company.teams.map(t =>
    t.id === 2 ? {...t, name: "Platform"} : t
  ),
};
const withNewMember = {
  ...company,
  teams: company.teams.map(t =>
    t.id === 1 ? {...t, memberIds: [...t.memberIds, 5]} : t
  ),
};
const withoutMember3 = {
  ...company,
  teams: company.teams.map(t =>
    t.id === 2
      ? {...t, memberIds: t.memberIds.filter(id => id !== 3)}
      : t
  ),
};
10

COMPOSE THE MOVES

Join data, then write the pipeline

lookup → flatMap · filter → map → sort

Finish by composing the earlier operations in dependency order. Keep each intermediate shape clear.

Write it in this order

  1. Build usersById before resolving member ids. Read the selected team with ?. and default missing memberIds to [].
  2. flatMap each id to [user] when found or [] when missing; this both joins and removes missing partners.
  3. For active names: filter while active is still available, map to name, then sort the resulting strings.
Remember
Pipeline order follows the data you still need. After mapping users to names, you no longer have the active property for filtering.
Watch for
A find inside every member loop costs O(mn); a lookup built once makes the join expected O(n + m). Sorting the new mapped array is safe for the original users array.
TRY FROM MEMORY

Resolve Frontend’s members and return alphabetized active names.

Check: [Ana, Ben]; [Ana, Cara]

TypeScript implementation Open code +
Includes sample data when copied
// Build this first — the join depends on it (stage 07).
const usersById = Object.fromEntries(users.map(u => [u.id, u]));

const frontendMembers = (company.teams[0]?.memberIds ?? []).flatMap(id => {
  const user = usersById[id];
  return user ? [user] : [];
});

const activeNames = users
  .filter(u => u.active)
  .map(u => u.name)
  .sort((a, b) => a.localeCompare(b));

THE FINAL CHECK

Can you combine the moves without looking?

Rename a team, add a member, resolve its member ids to users, keep only active users, and return their alphabetized names. First name the intermediate shape after each step. Then implement it.

n = users; k = object keys; t = teams; m = member ids; a = active users. String comparisons also depend on string length. Hash lookups use expected-time shorthand; sort performance depends on the engine.

Continue to the algorithm pattern reference →