Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@

**Learning:** In Next.js/React applications, when grouping items (like schedules or talks) into a `Map` where the values are arrays, using the array spread operator `[...existing, item]` inside a loop (like `forEach` or `map`) causes amortized O(N^2) memory allocations and unnecessary Garbage Collection overhead.
**Action:** Always use `.push()` on the existing array reference if the data structure permits local mutation. For strict ESLint configurations enforcing `no-restricted-syntax`, extract the existing array, push to it, and handle the fallback elegantly (`if (!existing) { map.set(key, [item]); } else { existing.push(item); }`).

## 2024-05-19 - Avoid flatMap and find for deep tag lookups in large datasets

**Learning:** When searching for a specific item in nested arrays (like finding a specific tag across all talks), using `allTalks.flatMap(getTags).find(...)` creates unnecessary intermediate arrays and forces multiple full O(N) traversals. This is particularly slow during build-time operations like `generateStaticParams` or tag pages rendering with many talks.
**Action:** Use nested `for...of` loops (or `.some()` with early return and state accumulation) to do a single-pass O(N) search with early breakout to minimize Garbage Collection overhead and CPU cycles.
31 changes: 26 additions & 5 deletions app/[year]/tags/[tag]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,21 @@ export async function generateMetadata({ params }: Readonly<TagPageProps>): Prom

const sessionGroups = await getTalks(year);
const allTalks = sessionGroups.flatMap((group) => group.sessions);
const displayTag =
allTalks.flatMap(getTagsFromTalk).find((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()) ?? decodedTag.replaceAll("-", " ");
const targetTagLower = decodedTag.toLowerCase();
const state = { displayTag: undefined as string | undefined };

for (const talk of allTalks) {
const talkTags = getTagsFromTalk(talk);
for (const t of talkTags) {
if (t.replaceAll(" ", "-").toLowerCase() === targetTagLower) {
state.displayTag = t;
break;
}
}
if (state.displayTag) break;
}

const displayTag = state.displayTag ?? decodedTag.replaceAll("-", " ");
Comment on lines +50 to +64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of using a nested loop with a mutable helper state object, you can simplify the early-break logic by using a standard for...of loop combined with .find(). This avoids the extra level of nesting and the state object, making the code more readable and maintainable.

  const targetTagLower = decodedTag.toLowerCase();
  let matchedTag: string | undefined;

  for (const talk of allTalks) {
    matchedTag = getTagsFromTalk(talk).find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTagLower);
    if (matchedTag) break;
  }

  const displayTag = matchedTag ?? decodedTag.replaceAll("-", " ");


return {
title: `Talks tagged "${displayTag}" - DevBcn ${year}`,
Expand All @@ -64,15 +77,23 @@ export default async function TagPage({ params }: Readonly<TagPageProps>) {
const sessionGroups = await getTalks(year);
const allTalks = sessionGroups.flatMap((group) => group.sessions);

const displayTag =
allTalks.flatMap(getTagsFromTalk).find((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase()) ?? decodedTag.replaceAll("-", " ");
const targetTagLower = decodedTag.toLowerCase();
const state = { displayTag: undefined as string | undefined };

const filteredTalks = allTalks.filter((talk) => {
const talkTags = getTagsFromTalk(talk);

return talkTags.some((t) => t.replaceAll(" ", "-").toLowerCase() === decodedTag.toLowerCase());
return talkTags.some((t) => {
if (t.replaceAll(" ", "-").toLowerCase() === targetTagLower) {
if (!state.displayTag) state.displayTag = t;
return true;
}
return false;
});
});

const displayTag = state.displayTag ?? decodedTag.replaceAll("-", " ");
Comment on lines +80 to +95

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Mutating external state (like state.displayTag) inside an array callback like filter is an anti-pattern because it introduces side effects into what should be a pure function. We can completely avoid the mutable state object and side effects by filtering the talks first, and then extracting the matching tag from the first filtered talk.

  const targetTagLower = decodedTag.toLowerCase();

  const filteredTalks = allTalks.filter((talk) =>
    getTagsFromTalk(talk).some((t) => t.replaceAll(" ", "-").toLowerCase() === targetTagLower)
  );

  const firstMatch = filteredTalks[0];
  const displayTag = firstMatch
    ? getTagsFromTalk(firstMatch).find((t) => t.replaceAll(" ", "-").toLowerCase() === targetTagLower) ?? decodedTag.replaceAll("-", " ")
    : decodedTag.replaceAll("-", " ");


if (filteredTalks.length === 0) {
notFound();
}
Expand Down
Loading