<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged performance</title>
<link>https://coderlegion.com/tag/performance</link>
<description>Powered by Question2Answer</description>
<item>
<title>What Is Idempotency?</title>
<link>https://coderlegion.com/33068/what-is-idempotency</link>
<description>&lt;p&gt;🚀 &lt;strong&gt;What Happens When Your API Receives the Same Request Twice?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Imagine a customer clicks &lt;strong&gt;“Pay Now”&lt;/strong&gt; twice because the page freezes.&lt;/p&gt;
&lt;p&gt;Or a payment gateway retries a request because it never received your response.&lt;/p&gt;
&lt;p&gt;💥 Without proper protection, your application might charge the customer twice!&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;Idempotency&lt;/strong&gt; becomes essential.&lt;/p&gt;
&lt;h4 id=&quot;-what-is-idempotency-&quot;&gt;🔍 What Is Idempotency?&lt;/h4&gt;
&lt;p&gt;Idempotency means that &lt;strong&gt;performing the same operation multiple times produces the same result as performing it once.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For example, if a client retries a payment request, the system should return the original result instead of creating another payment.&lt;/p&gt;
&lt;h4 id=&quot;-how-do-we-implement-it-in-ruby-on-rails-&quot;&gt;🛠️ How Do We Implement It in Ruby on Rails?&lt;/h4&gt;
&lt;p&gt;A common approach is using an &lt;strong&gt;Idempotency-Key&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The client sends a unique key with the request:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;POST /api/v1/payments
Idempotency-Key: order-123-payment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rails can use that key to identify duplicate requests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Add a database constraint&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;add_index :payments, :idempotency_key, unique: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Store the key with the payment&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;Payment.create!(
  idempotency_key: idempotency_key,
  amount: amount,
  status: &quot;pending&quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; This is only a simplified example. A production implementation must handle duplicate-key exceptions, concurrent requests, and retries safely.&lt;/p&gt;
&lt;p&gt;You should also define what happens when the same key is reused with different request parameters.&lt;/p&gt;
&lt;p&gt;For payment APIs, store and return the original result for a matching key rather than processing the payment again.&lt;/p&gt;
&lt;h4 id=&quot;-why-is-idempotency-important-&quot;&gt;💎 Why Is Idempotency Important?&lt;/h4&gt;
&lt;p&gt;✅ &lt;strong&gt;Prevents Duplicate Payments&lt;/strong&gt; — Avoid charging customers twice when requests are retried.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Makes Retries Safer&lt;/strong&gt; — Network failures become easier to handle.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Improves API Reliability&lt;/strong&gt; — Clients can retry requests without accidentally repeating business operations.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Protects Background Jobs&lt;/strong&gt; — Prevent duplicate emails, orders, or other side effects when jobs run more than once.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Supports Distributed Systems&lt;/strong&gt; — Helps manage unreliable networks and repeated messages.&lt;/p&gt;
&lt;h4 id=&quot;-one-important-detail&quot;&gt;⚠️ One Important Detail&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Idempotency is not just checking whether a request has been received before.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You must handle concurrent requests, database transactions, unique constraints, and external service calls carefully.&lt;/p&gt;
&lt;p&gt;For example, a database transaction alone cannot guarantee that an external payment provider won't charge a customer twice.&lt;/p&gt;
&lt;p&gt;💡 &lt;strong&gt;The goal is simple: make retries safe, even when failures happen.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because in real-world applications, requests will fail, networks will time out, and background jobs will retry.&lt;/p&gt;
&lt;p&gt;Your architecture should be ready for it!&lt;/p&gt;
&lt;p&gt;💬 &lt;strong&gt;How do you handle duplicate requests in your Rails applications?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;rubyonrails-api-idempotency-backenddevelopment-softwarearchitecture-distributedsystems-webdevelopment-softwareengineering&quot;&gt;RubyOnRails #API #Idempotency #BackendDevelopment #SoftwareArchitecture #DistributedSystems #WebDevelopment #SoftwareEngineering&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33068/what-is-idempotency</guid>
<pubDate>Fri, 30 Oct 2026 09:28:35 +0000</pubDate>
</item>
<item>
<title>Redux Refactoring Playbook: From Action-Types Soup to RTK Query — Without Rewriting the App</title>
<link>https://coderlegion.com/32949/redux-refactoring-playbook-from-action-types-soup-to-rtk-query-without-rewriting-the-app</link>
<description>&lt;p&gt;Every React codebase of a certain age has one: the legacy Redux store. Hundreds of thunk actions, manually managed loading flags, per-feature cache invalidation, and a polling timer per data type pulling the full payload whether anything changed or not.&lt;/p&gt;
&lt;p&gt;Ours lived under a real-time monitoring dashboard tracking 1,000+ GSM installations at Orange Romania — and by the time we migrated to RTK Query, the story ended with dashboard responsiveness up ~40%, telemetry traffic down 20%, and &quot;frozen dashboard&quot; complaints down 30%.&lt;/p&gt;
&lt;p&gt;The previous article told that performance story. This one is the refactoring &lt;em&gt;mechanics&lt;/em&gt;: in what order things moved, which decisions unlocked the next one, and the one rule that made a no-rewrite migration possible.&lt;/p&gt;
&lt;h3 id=&quot;rule-1-strangle-don-t-rewrite&quot;&gt;Rule 1 — Strangle, don't rewrite&lt;/h3&gt;
&lt;p&gt;The store wasn't broken in one place; it was broken &lt;em&gt;everywhere&lt;/em&gt;: a switch-case reducer file nobody fully read, five middleware, &lt;code&gt;isLoading&lt;/code&gt; / &lt;code&gt;isRefreshing&lt;/code&gt; / &lt;code&gt;hasError&lt;/code&gt; flag families per resource, and component-level &lt;code&gt;useEffect&lt;/code&gt; chains firing fetches in a specific order &quot;because it worked.&quot;&lt;/p&gt;
&lt;p&gt;A big-bang rewrite was a non-starter: the dashboard was used every day by operations teams. So the rule was:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One page migrates per PR. The rest of the app keeps running on the legacy store untouched.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Redux makes this possible because &lt;code&gt;combineReducers&lt;/code&gt; is natural strangler scaffolding:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const rootReducer = combineReducers({
  // new pages live here
  ordersApi: ordersApi.reducer,          // RTK Query
  invoices: invoicesSlice.reducer,       // RTK slice
  // legacy pages keep their ducks until their turn
  legacyTelemetry: legacyTelemetryReducer, // switch-case reducer, retired last
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each migration PR carried its own before/after render counts and INP p75 from a fixed scripted interaction. If the numbers moved the wrong way, the PR didn't merge — refactoring without a measurement loop is just rewriting with extra steps.&lt;/p&gt;
&lt;p&gt;The code was the easy half. The pitch mattered as much: I sold this as risk control, not cleanup. Every PR shippable alone meant no feature-freeze window that anyone had to buy; the cost I did have to name up front was the parallel-store tax: two stores side by side meant duplicated fixtures and slower reviews while a slice waited for its turn. Leadership signed off because every migration PR ended with a measurement, not with the word &quot;done&quot;. One more org-side lesson: the change-flag contract needed a backend buy-in, and I opened that conversation politely, late. Open it on day one; that delay cost us a month.&lt;/p&gt;
&lt;h3 id=&quot;rule-2-selectors-are-the-api-freeze-them-first-&quot;&gt;Rule 2 — Selectors are the API. Freeze them first.&lt;/h3&gt;
&lt;p&gt;The trick that removed 90% of the migration risk: &lt;strong&gt;components never touched state shapes directly.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The legacy store had selector functions (&lt;code&gt;selectOrders&lt;/code&gt;, &lt;code&gt;selectNodeById&lt;/code&gt;, &lt;code&gt;selectIsNodeActive&lt;/code&gt;). Before migrating anything, we froze them as the contract and then made each selector a re-export shim that could source data from either world:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// selectors/orders.js
import { legacyOrders } from './legacySelectors';
import { useGetOrdersQuery } from '../api/ordersApi';

export function useOrders(selector) {
  const { data, isFetching } = useGetOrdersQuery();
  // legacy pages keep working against the same contract
  return useMemo(() =&amp;gt; selector(data ?? legacyOrders()), [data, selector]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Simplified — the real shims carried fallbacks during the window when a page's data half-lived in the old store. The point is the shape: &lt;strong&gt;the component-facing API never moved, only its plumbing did.&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;Result: a &quot;migration PR&quot; changed the data layer under a page, not forty components over it. That made page-by-page migration realistic rather than a nice idea.&lt;/p&gt;
&lt;h3 id=&quot;rule-3-kill-loading-flags-with-rtk-query-not-with-better-thunks&quot;&gt;Rule 3 — Kill loading flags with RTK Query, not with better thunks&lt;/h3&gt;
&lt;p&gt;There was a middle step available: keep the thunks and clean them up. I'd have kept every race. Request A finishing after request B silently overwrote fresher data, and that was our most common production bug, because each flag family carried its own private version of it.&lt;/p&gt;
&lt;p&gt;So server state moved into RTK Query, and components declare what they need, not how to fetch it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// before: every call site, every page
dispatch(fetchOrders());
// + component-local: isLoading, ordersError, lastRefreshed...

// after: the component declares the requirement
const { data: orders, isLoading } = useGetOrdersQuery(undefined, {
  pollingInterval: 0, // see Rule 4 — we don't poll blindly
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seventeen loading-flag families disappeared in this step. After it, a component tracking its own fetch lifecycle existed in exactly one place: a page that legitimately needed manual control.&lt;/p&gt;
&lt;h3 id=&quot;rule-4-polling-moves-from-timers-to-tags&quot;&gt;Rule 4 — Polling moves from timers to tags&lt;/h3&gt;
&lt;p&gt;Old polling: one &lt;code&gt;setInterval&lt;/code&gt; per data type, full payload every tick. On a dashboard watching 1,000+ nodes, that's thousands of identical requests an hour and constant re-render churn over data that hadn't changed.&lt;/p&gt;
&lt;p&gt;New polling is no polling — it's a &lt;strong&gt;change-flag endpoint&lt;/strong&gt;: a ten-byte version string the dashbo&lt;br&gt;
ard polls on a timer, and the real data refetches when that string actually changes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: ['Nodes'],
  endpoints: (build) =&amp;gt; ({
    nodesChanged: build.query&amp;lt;{ version: string }, void&amp;gt;({
      query: () =&amp;gt; 'nodes/changed',       // returns a version string only
    }),
    getNodes: build.query&amp;lt;NodeState[], void&amp;gt;({
      query: () =&amp;gt; 'nodes',
      providesTags: ['Nodes'],
    }),
    // refetch tagged data only when the flag version changes:
    // a listener middleware polls `nodesChanged` (cheap, ~10 lines) and,
    // on version change, dispatches api.util.invalidateTags(['Nodes'])
  }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const flagListener = createListenerMiddleware();

flagListener.startListening({
  predicate: (action, currentState, previousState) =&amp;gt; {
    // the version lives in RTK Query's own cache, so the store
    // snapshot gives us both &quot;current&quot; and &quot;what it used to be&quot;
    const cur = nodesChanged.select()(currentState)?.data;
    const prev = nodesChanged.select()(previousState)?.data;
    return cur != null &amp;amp;&amp;amp; cur !== prev;
  },
  effect: (action, api) =&amp;gt; {
    api.dispatch(api.util.invalidateTags(['Nodes']));
  },
});

// the flag endpoint itself: cheap on purpose
//   query: () =&amp;gt; 'nodes/changed',   // returns ~10 bytes: a version string
//   pollingInterval: 5000,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What happens on an update, end to end: the version goes into the cache and&lt;br&gt;
&lt;code&gt;predicate&lt;/code&gt; sees it differ from the previous snapshot, &lt;code&gt;invalidateTags&lt;/code&gt; marks&lt;br&gt;
every tagged entry stale &lt;strong&gt;without removing it&lt;/strong&gt; (the screen keeps the old&lt;br&gt;
data), mounted subscribers immediately refetch — deduplicated to one request&lt;br&gt;
per endpoint+args no matter how many components listen — and one re-render&lt;br&gt;
lands the new data. A component sees &lt;code&gt;isLoading: false, isFetching: true&lt;/code&gt;&lt;br&gt;
during that window, which is why nothing flashes. If the flag endpoint dies,&lt;br&gt;
data simply goes stale until the next successful tick; nothing breaks. If&lt;br&gt;
nodes changed five times between two ticks, one refetch covers it: the&lt;br&gt;
version is a snapshot of &quot;what is true now&quot;, not an event queue.&lt;/p&gt;
&lt;h3 id=&quot;what-stayed-in-redux-core&quot;&gt;What stayed in Redux core&lt;/h3&gt;
&lt;p&gt;A mistake we avoided (mostly): RTK Query is for &lt;strong&gt;server state only&lt;/strong&gt;. UI-owned state — selected node, filter panel state, column layout, wizard steps — stayed in plain &lt;code&gt;createSlice&lt;/code&gt;. Migrating that into queries would have replaced a working pattern with a worse one.&lt;/p&gt;
&lt;p&gt;The end state: slices for decisions, RTK Query for data, zero custom cache code, zero hand-rolled loading flags.&lt;/p&gt;
&lt;h3 id=&quot;the-honest-ledger&quot;&gt;The honest ledger&lt;/h3&gt;
&lt;p&gt;From the published case, measured as &lt;em&gt;perceived dashboard responsiveness&lt;/em&gt; (how fast fresh data lands after a change, without UI-thread churn):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dashboard responsiveness: &lt;strong&gt;+~40%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&quot;Frozen dashboard&quot; complaints: &lt;strong&gt;−30%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;REST telemetry traffic: &lt;strong&gt;−20%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I can't give you is a LOC table with a precise count — we deleted more code than we wrote every migration PR, and by the end the store directory had visibly halved; I kept no exact tally, and a made-up precision here would be dishonest.&lt;/p&gt;
&lt;h3 id=&quot;what-i-d-do-differently&quot;&gt;What I'd do differently&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Start with RTK Query for any new page.&lt;/strong&gt; Half the migration tail was legacy thunks for features that could have been endpoints from day one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Freeze selectors before touching anything.&lt;/strong&gt; The component-facing API is the migration's unit of safety.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Measure per PR, not per epic.&lt;/strong&gt; The scripted interaction + INP check cost minutes per PR and caught two regressions a &quot;it looks fine&quot; test never would.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write the change-flag backend contract early.&lt;/strong&gt; The polling redesign was the single biggest win and it needed a backend buy-in — politeness about it cost us a month.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;your-refactoring-checklist&quot;&gt;Your refactoring checklist&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Inventory actions/thunks/loading flags — count them, this is your &quot;before&quot; baseline.&lt;/li&gt;
&lt;li&gt;Freeze selectors as the component-facing contract.&lt;/li&gt;
&lt;li&gt;Add RTK Query via &lt;code&gt;combineReducers&lt;/code&gt; alongside the legacy store.&lt;/li&gt;
&lt;li&gt;Migrate one page per PR; measure INP p75 + render count on a fixed interaction.&lt;/li&gt;
&lt;li&gt;Server state → endpoints with tags; UI state stays in slices.&lt;/li&gt;
&lt;li&gt;Replace timers with a change-flag contract before touching fetch logic.&lt;/li&gt;
&lt;li&gt;Delete: action-type strings, flag families, custom cache code. The deletion count is the score.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The performance numbers come from the same production system as my earlier piece, &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/veaceslav/a-40-faster-fault-detection-pipeline-without-websockets-the-polling-trick-we-shipped-e28&quot;&gt;A 40% Faster Fault-Detection Pipeline Without WebSockets&lt;/a&gt; — that one tells the polling-protocol story; this is the data-layer refactoring around it.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32949/redux-refactoring-playbook-from-action-types-soup-to-rtk-query-without-rewriting-the-app</guid>
<pubDate>Thu, 29 Oct 2026 08:51:02 +0000</pubDate>
</item>
<item>
<title>You Upgraded to Next.js 15 and Your Site Got Slower. The Caching Defaults Flipped</title>
<link>https://coderlegion.com/32719/you-upgraded-to-next-js-15-and-your-site-got-slower-the-caching-defaults-flipped</link>
<description>&lt;p&gt;You upgrade from Next.js 14 to 15. The app compiles, the tests pass, nothing visibly breaks. A week later your database bill is higher, your pages feel slower, and your Time to First Byte graph has a step in it that lines up exactly with the deploy.&lt;/p&gt;
&lt;p&gt;Nothing is broken. Something that used to happen for free stopped happening.&lt;/p&gt;
&lt;h3 id=&quot;what-changed&quot;&gt;What Changed&lt;/h3&gt;
&lt;p&gt;In Next.js 14, data fetching was cached &lt;strong&gt;by default&lt;/strong&gt;. A &lt;code&gt;fetch&lt;/code&gt; in a Server Component ran once, the result was stored, and later requests reused it. You had to opt &lt;em&gt;out&lt;/em&gt; of caching.&lt;/p&gt;
&lt;p&gt;In Next.js 15, the default flipped. Per the upgrade notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;fetch&lt;/code&gt; requests are no longer cached by default.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;GET&lt;/code&gt; route handlers are no longer cached by default.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The client-side router cache no longer holds page segments between navigations by default.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So code you wrote once and never touched now behaves differently:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/products/page.tsx
export default async function ProductsPage() {
  const res = await fetch('https://api.example.com/products');
  const products = await res.json();
  return &amp;lt;ProductList products={products} /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On 14, this fetched the data once and served the page from cache. On 15, &lt;strong&gt;every visitor triggers the API call&lt;/strong&gt;, and because the page now depends on data fetched at request time, the route is rendered dynamically.&lt;/p&gt;
&lt;h3 id=&quot;why-it-is-easy-to-miss&quot;&gt;Why It Is Easy to Miss&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nothing errors.&lt;/strong&gt; The code is valid. It just does more work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dev mode hides it.&lt;/strong&gt; In &lt;code&gt;next dev&lt;/code&gt;, nearly everything is rendered per request anyway.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The slowdown is gradual.&lt;/strong&gt; With low traffic, an extra API call per request is invisible. It shows up when the traffic does.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Third-party limits bite later.&lt;/strong&gt; If the API you call has rate limits or per-request billing, you find out from the invoice.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;how-to-see-it-in-30-seconds&quot;&gt;How to See It in 30 Seconds&lt;/h3&gt;
&lt;p&gt;Run a production build and read the route table at the end:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;next build
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Route (app)
┌ ○ /                 (Static)
├ ƒ /products         (Dynamic)
└ ƒ /blog/[slug]      (Dynamic)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;○&lt;/code&gt; means the page is pre-rendered once at build time. &lt;code&gt;ƒ&lt;/code&gt; means it runs on the server for every request. A page that used to be static and is now &lt;code&gt;ƒ&lt;/code&gt; is exactly what this upgrade can cause. Compare the table before and after the upgrade.&lt;/p&gt;
&lt;h3 id=&quot;fix-1-cache-one-request-on-purpose&quot;&gt;Fix 1: Cache One Request on Purpose&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const res = await fetch('https://api.example.com/products', {
  cache: 'force-cache',
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This restores the old behavior for that single request. Use it for data that does not change per user and rarely changes at all.&lt;/p&gt;
&lt;h3 id=&quot;fix-2-cache-with-an-expiry&quot;&gt;Fix 2: Cache With an Expiry&lt;/h3&gt;
&lt;p&gt;Most data is not permanent, just slow-changing. Revalidate it on a timer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const res = await fetch('https://api.example.com/products', {
  next: { revalidate: 3600 }, // seconds
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Visitors get the cached copy, and after an hour the next request refreshes it in the background. This is the right default for product lists, blog posts and prices.&lt;/p&gt;
&lt;h3 id=&quot;fix-3-set-it-once-for-the-whole-page&quot;&gt;Fix 3: Set It Once for the Whole Page&lt;/h3&gt;
&lt;p&gt;If several fetches on a page should share the same freshness, declare it at the route level:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/products/page.tsx
export const revalidate = 3600;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything that page renders is treated as cacheable for an hour, without repeating options on each call.&lt;/p&gt;
&lt;h3 id=&quot;fix-4-route-handlers-need-their-own-opt-in&quot;&gt;Fix 4: Route Handlers Need Their Own Opt-In&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;GET&lt;/code&gt; route handler that returns the same data for everyone used to be cached automatically. Now you must say so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// app/api/products/route.ts
export const dynamic = 'force-static';

export async function GET() {
  const products = await getProducts();
  return Response.json(products);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without that line, the handler runs on every request.&lt;/p&gt;
&lt;h3 id=&quot;the-part-people-forget-your-database-was-never-covered&quot;&gt;The Part People Forget: Your Database Was Never Covered&lt;/h3&gt;
&lt;p&gt;Here is the subtle thing. The &lt;code&gt;fetch&lt;/code&gt; cache only ever applied to &lt;code&gt;fetch&lt;/code&gt;. If you read from MongoDB or Postgres directly with an ORM, this change did not touch those calls. Depending on whether the page used request data such as cookies, it was either rendered once at build time with the data frozen in, or rendered on every request. Neither is controlled by the fetch cache, so check your build table for those pages too.&lt;/p&gt;
&lt;p&gt;To cache a database call at runtime, wrap it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { unstable_cache } from 'next/cache';

const getProducts = unstable_cache(
  async () =&amp;gt; Product.find().lean(),
  ['products'],
  { revalidate: 3600, tags: ['products'] }
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;tags&lt;/code&gt; option lets you clear that cache on demand with &lt;code&gt;revalidateTag('products')&lt;/code&gt; after an edit. I wrote about why &lt;code&gt;revalidateTag&lt;/code&gt; appears to do nothing when the data is not tagged in the first place. Newer Next.js versions keep moving toward explicit opt-in caching, so check the docs for the version you are on before copying any of this.&lt;/p&gt;
&lt;h3 id=&quot;should-you-cache-everything-again-&quot;&gt;Should You Cache Everything Again?&lt;/h3&gt;
&lt;p&gt;No. The new default exists because stale data surprised people. Show a user their own cart, balance or notifications from a shared cache and you have a bug, or a data leak.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Data &lt;/th&gt;&lt;th&gt;Choice&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Public, rarely changes (marketing pages, docs) &lt;/td&gt;&lt;td&gt;Cache it, long expiry&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Public, changes often (product list, prices) &lt;/td&gt;&lt;td&gt;Cache with a short &lt;code&gt;revalidate&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Per-user (dashboard, cart, account) &lt;/td&gt;&lt;td&gt;Do not cache&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Admin views &lt;/td&gt;&lt;td&gt;Do not cache&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;the-rule&quot;&gt;The Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;When a framework changes a default, your old code changes behavior without a single line changing.&lt;/strong&gt; After any major upgrade, run &lt;code&gt;next build&lt;/code&gt; before and after and compare which routes are static (&lt;code&gt;○&lt;/code&gt;) and which are dynamic (&lt;code&gt;ƒ&lt;/code&gt;). Then opt back in on purpose, for the data that deserves it.&lt;/p&gt;
&lt;p&gt;Slow server responses hurt the metrics Google measures. I break those down in plain language in my &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com/blog/core-web-vitals-explained&quot;&gt;guide to Core Web Vitals&lt;/a&gt;, and if you want a Next.js starting point, you can browse my &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com&quot;&gt;templates on pixelanas.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Did a framework upgrade ever quietly change your app's behavior? Tell me what it was in the comments.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32719/you-upgraded-to-next-js-15-and-your-site-got-slower-the-caching-defaults-flipped</guid>
<pubDate>Tue, 27 Oct 2026 16:13:23 +0000</pubDate>
</item>
<item>
<title>Seventeen Seconds to Parse One Name: A Security Scan Across Eleven PHP Projects</title>
<link>https://coderlegion.com/32689/seventeen-seconds-to-parse-one-name-a-security-scan-across-eleven-php-projects</link>
<description>&lt;p&gt;This is a 16 KB name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$row = 'Smith, ' . str_repeat('V ', 8188);
(new \Iliaal\NameParser\Parser())-&amp;gt;parse($row);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On nameparser 1.5.0, the last version before the fix, that call takes 17.4 seconds of CPU on my i9-13950HX. On 1.7.0 it takes 0.064 seconds. Each &lt;code&gt;V&lt;/code&gt; is a candidate roman-numeral suffix, and the old suffix scan walked back over every earlier token for each one, so the cost grew with the square of the input. Nothing about it is exotic. It is a plausible shape for a junk row in a CSV import, and it pins a PHP worker for as long as the row keeps coming.&lt;/p&gt;
&lt;p&gt;I didn't find it. A security scan did, and the same scan ran across all ten of my PHP extensions as well. In the first three weeks of October each project got a full pass: one coordinating session, a set of subagents reading code and writing proof-of-concept exploits, and a separate verifier that re-ran every finding before it counted, on its own ASan and release builds for the C code. Everything below ships in releases from the last few weeks.&lt;/p&gt;
&lt;p&gt;Two other things landed in the same window. PHP's October security releases changed what &lt;code&gt;memory_limit&lt;/code&gt; does, which forced code changes in three extensions and a same-day round of re-releases in five more. And DuckDB 2.0 went GA; pdo_duckdb bundles it as of today.&lt;/p&gt;
&lt;h3 id=&quot;what-the-scans-found-and-what-is-fixed&quot;&gt;What the scans found, and what is fixed&lt;/h3&gt;
&lt;p&gt;Every project in the table has a release that carries its fixes. The counts are findings confirmed by the verifier, fixed, and released; severities are the verifier's, not the scanner's first guess.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Project &lt;/th&gt;&lt;th&gt;Fixed in &lt;/th&gt;&lt;th&gt;Confirmed and fixed&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/iliaal/phpser/releases/tag/0.7.1&quot;&gt;phpser&lt;/a&gt; &lt;/td&gt;&lt;td&gt;0.7.1 &lt;/td&gt;&lt;td&gt;19: 1 high, 5 medium, 13 low&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/iliaal/mdparser/releases/tag/0.8.0&quot;&gt;mdparser&lt;/a&gt; &lt;/td&gt;&lt;td&gt;0.8.0 &lt;/td&gt;&lt;td&gt;12: 1 high, 5 medium, 5 low, 1 informational&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/iliaal/fastchart/releases/tag/1.8.0&quot;&gt;fastchart&lt;/a&gt; &lt;/td&gt;&lt;td&gt;1.8.0 &lt;/td&gt;&lt;td&gt;15 reproduced, highest medium, including vendored plutovg and plutosvg; two UBSan sites in vendored code only partly addressed&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/iliaal/php_clickhouse/releases/tag/0.13.0&quot;&gt;php_clickhouse&lt;/a&gt; &lt;/td&gt;&lt;td&gt;0.13.0 &lt;/td&gt;&lt;td&gt;1 high, 1 medium, plus low-severity binding fixes and 7 server-triggered crashes in clickhouse-cpp&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/iliaal/pdo_duckdb/releases/tag/0.8.0&quot;&gt;pdo_duckdb&lt;/a&gt; &lt;/td&gt;&lt;td&gt;0.8.0 &lt;/td&gt;&lt;td&gt;6: 1 high, 1 medium, 1 low-medium, 2 low, 1 informational&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/iliaal/fastjson/releases/tag/0.10.0&quot;&gt;fastjson&lt;/a&gt; &lt;/td&gt;&lt;td&gt;0.10.0 &lt;/td&gt;&lt;td&gt;4: 1 medium, 3 low, plus 1 documentation correction&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/iliaal/php_excel/releases/tag/2.9.0&quot;&gt;php_excel&lt;/a&gt; &lt;/td&gt;&lt;td&gt;2.9.0 &lt;/td&gt;&lt;td&gt;5: 3 low, 2 informational&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/iliaal/statgrab/releases/tag/2.3.0&quot;&gt;statgrab&lt;/a&gt; &lt;/td&gt;&lt;td&gt;2.3.0 &lt;/td&gt;&lt;td&gt;4: 2 medium, 2 low, plus a build bug&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/iliaal/fast_uuid/releases/tag/0.8.1&quot;&gt;fast_uuid&lt;/a&gt; &lt;/td&gt;&lt;td&gt;0.8.1 &lt;/td&gt;&lt;td&gt;5: 4 low, 1 by-design behaviour changed anyway&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/iliaal/phonetic/releases/tag/0.4.4&quot;&gt;phonetic&lt;/a&gt; &lt;/td&gt;&lt;td&gt;0.4.4 &lt;/td&gt;&lt;td&gt;3: 1 medium, 2 low&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/iliaal/nameparser/releases/tag/1.6.0&quot;&gt;nameparser&lt;/a&gt; &lt;/td&gt;&lt;td&gt;1.6.0 &lt;/td&gt;&lt;td&gt;5: 1 medium, 1 low, 3 informational&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A few findings in upstream libraries are reported privately to their maintainers and aren't described here until fixes ship.&lt;/p&gt;
&lt;p&gt;The coordinating model was Mythos 5.1, but most of the work ran on other models: on fastjson, 695 of the 702 assistant messages came from Opus 5.5 subagents doing the reading, fuzzing, and PoC writing. The phpser scan doubled as an A/B/C test of my own tooling against a bare prompt, which I &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/iliaa/status/2109324437501718830&quot;&gt;wrote up on X&lt;/a&gt; at the time. So the table measures the process more than any one model.&lt;/p&gt;
&lt;p&gt;fast_uuid and phonetic came back with zero memory-safety bugs, and php_excel's wrapper with nothing above low severity.&lt;/p&gt;
&lt;h4 id=&quot;bugs-that-needed-no-attacker&quot;&gt;Bugs that needed no attacker&lt;/h4&gt;
&lt;p&gt;The worst php_clickhouse finding in practice wasn't the high-severity one. It was B-4, which corrupted data on ordinary queries. Decimal and DateTime64 decoding cached each column's scale in a memo keyed on the raw address of the column's type object. clickhouse-cpp allocates a fresh type object per column per block and frees the block after each callback, so the next block's first column could land at the same address and inherit the previous column's scale. Select a scale-2 column next to a scale-6 one with &lt;code&gt;max_block_size=100&lt;/code&gt;, and a stored &lt;code&gt;12.34&lt;/code&gt; came back as &lt;code&gt;0.001234&lt;/code&gt;, with no error. With default settings the verifier still saw 1 wrong cell in 70,000 rows.&lt;/p&gt;
&lt;p&gt;The test suite never caught it because ASan builds hide it: the sanitizer's quarantine stops freed memory from being reused, so the address never repeats. It took a release build and a real server. 0.13.0 keys the memo correctly.&lt;/p&gt;
&lt;p&gt;0.13.0 also fixes a behaviour you should check for before upgrading. A whole-number insert into a Decimal column, &lt;code&gt;7&lt;/code&gt; into &lt;code&gt;Decimal(9,2)&lt;/code&gt;, now stores &lt;code&gt;7.00&lt;/code&gt; as the server does; 0.12 stored &lt;code&gt;0.07&lt;/code&gt;. Code that compensated by passing pre-scaled integers (&lt;code&gt;700&lt;/code&gt; meaning 7.00) will now store &lt;code&gt;700.00&lt;/code&gt; without complaint.&lt;/p&gt;
&lt;p&gt;fast_uuid's main finding has the same character. &lt;code&gt;uuid1()&lt;/code&gt; and &lt;code&gt;uuid6()&lt;/code&gt; with a fixed node and clock sequence kept no last-tick state, so &lt;code&gt;uuid1(1, 7)&lt;/code&gt; called 500,000 times produced 61,723 duplicates. The default arguments were fine. 0.8.1 fixes it.&lt;/p&gt;
&lt;h4 id=&quot;bugs-a-server-a-file-or-a-document-could-trigger&quot;&gt;Bugs a server, a file, or a document could trigger&lt;/h4&gt;
&lt;p&gt;php_clickhouse's high-severity finding was a heap overflow write. With compression enabled, a malicious or compromised server could send a compressed frame whose declared size was smaller than the 9-byte frame header, and the client wrote past its buffer with bytes the server chose. clickhouse-cpp had already fixed it on master in &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ClickHouse/clickhouse-cpp/pull/566&quot;&gt;PR #566&lt;/a&gt;, but no clickhouse-cpp release contains the fix yet, and php_clickhouse's prebuilt binaries ship the library. 0.13.0 carries the patch. The same release rejects seven more server-triggered crash shapes, from &lt;code&gt;FixedString(0)&lt;/code&gt; columns to &lt;code&gt;Array&lt;/code&gt; offsets that run backwards, and stops a packet of a few dozen bytes from committing gigabytes of memory outside &lt;code&gt;memory_limit&lt;/code&gt;. I sent the fixes upstream as 15 &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ClickHouse/clickhouse-cpp/pulls?q=author%3Ailiaal&quot;&gt;pull requests&lt;/a&gt;; three have merged so far and twelve are open.&lt;/p&gt;
&lt;p&gt;fastchart's two medium findings were both resource bombs. A 44 KB SVG passed to &lt;code&gt;svgTo*()&lt;/code&gt; reached 2,121,264 KiB of resident memory through data URIs and &lt;code&gt;&amp;lt;use&amp;gt;&lt;/code&gt; references that slipped past the input gate, all of it outside &lt;code&gt;memory_limit&lt;/code&gt;; a 30,000-deep &lt;code&gt;&amp;lt;use&amp;gt;&lt;/code&gt; chain crashed the process. A 1 MB CgBI PNG with decoy dimensions decoded into a 16000 by 16000 buffer. 1.8.0 runs the gate on the parsed tree, bounds &lt;code&gt;&amp;lt;use&amp;gt;&lt;/code&gt; depth and expansion, and caps decoded images at 4,096 pixels a side. Some heavy but legitimate SVG that used to render slowly now throws a &lt;code&gt;ValueError&lt;/code&gt; instead, so test your largest inputs.&lt;/p&gt;
&lt;p&gt;mdparser's high-severity finding was quadratic parsing: blank lines inside deeply nested lists took 1.7 seconds at 200 KB and 55 seconds at 1 MB. Seven of mdparser's thirteen findings had their root cause in md4c rather than in my code, and the fixes went upstream; md4c merged five of my pull requests for them on October 13. One remaining low-severity signed overflow is still waiting in &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mity/md4c/pull/374&quot;&gt;md4c#374&lt;/a&gt;. 0.8.0 also changes generated heading IDs to start with &lt;code&gt;user-content-&lt;/code&gt;, which closes a DOM-clobbering finding and breaks existing links to heading fragments when &lt;code&gt;headingAnchors&lt;/code&gt; is on.&lt;/p&gt;
&lt;h4 id=&quot;the-other-high-and-medium-findings&quot;&gt;The other high and medium findings&lt;/h4&gt;
&lt;p&gt;pdo_duckdb's high finding was a use-after-free: an appender left open inside a reference cycle could outlive its PDO handle when &lt;code&gt;gc_collect_cycles()&lt;/code&gt; or request shutdown freed the handle first. Two of the medium-range findings were the same shape. A &lt;code&gt;memory_limit&lt;/code&gt; bailout at the wrong moment during column setup left uninitialised type pointers that were destroyed later; the PoC got the sanitizer to report a destructor running on &lt;code&gt;0x4141414141414141&lt;/code&gt;. 0.8.0 fixes all six.&lt;/p&gt;
&lt;p&gt;phpser's high finding was that a nested SPL &lt;code&gt;unserialize()&lt;/code&gt; ignored &lt;code&gt;allowed_classes&lt;/code&gt;. Its legacy handler path ran outside the decoder's context, so a class allowlist set on the outer call never reached the inner one. 0.7.1 fixes it along with eighteen others, most of them bounds on work an untrusted payload could demand.&lt;/p&gt;
&lt;p&gt;nameparser's quadratic case, from the top of this post, is fixed in 1.6.0. Its release notes say plainly that earlier notes describing hostile-input parsing as linear &quot;did not hold for these shapes&quot;. phonetic's medium finding was a 14-byte input, &lt;code&gt;bmpm(&quot;de akeaxhaeasz&quot;)&lt;/code&gt;, that caused an uncatchable fatal error. 0.4.4 turns budget overruns into a catchable &lt;code&gt;ValueError&lt;/code&gt; and caps BMPM output at 256 times the input size, after a 1,560-byte input had produced 3.9 MB.&lt;/p&gt;
&lt;h3 id=&quot;memory-limit-stopped-stopping-things&quot;&gt;memory_limit stopped stopping things&lt;/h3&gt;
&lt;p&gt;PHP 8.2.35, 8.3.36, 8.4.27, and 8.5.12 shipped on October 21 with the fix for &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w&quot;&gt;CVE-2026-109618&lt;/a&gt;, rated critical. Part of that fix changes what happens when an allocation crosses &lt;code&gt;memory_limit&lt;/code&gt;. The engine used to bail out on the spot with a longjmp. Now it records the error, lets the allocation succeed, and raises the fatal at the next safe point in the VM.&lt;/p&gt;
&lt;p&gt;For PHP code that's invisible. For an extension it isn't. A C loop that decodes a payload or builds rows never reaches a VM safe point, so it keeps allocating past &lt;code&gt;memory_limit&lt;/code&gt; until it hits a hard limit, by default twice &lt;code&gt;memory_limit&lt;/code&gt; below 1 GiB. At the hard limit the engine calls &lt;code&gt;_exit()&lt;/code&gt;: no shutdown functions, no destructors, no session write, and php-fpm loses the worker instead of failing one request.&lt;/p&gt;
&lt;p&gt;So every loop whose length comes from input has to notice the pending error and stop on its own. This is how phpser does it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;/* PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12 queue a memory_limit error instead of
 * bailing out; the allocation still succeeds until a hard limit (2x the soft
 * limit below 1G) that _exit()s the process. Loops that keep allocating must
 * stop on their own once the error is pending. */
#ifdef ZEND_HAS_OOM
# define PHPSER_HAS_OOM() ZEND_HAS_OOM()
#else
# define PHPSER_HAS_OOM() 0
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A loop check cannot help when a single allocation is sized from a count the payload declares. Those go through the new fallible allocators instead (&lt;code&gt;zend_hash_real_finit(..., true)&lt;/code&gt;, &lt;code&gt;safe_femalloc&lt;/code&gt;, &lt;code&gt;fecalloc&lt;/code&gt;), which queue the error and return &lt;code&gt;NULL&lt;/code&gt; rather than running into the hard limit. phpser 0.10.0 uses both. mdparser 0.8.1 and pdo_duckdb 0.9.0 do the same for their input-sized loops and presizes.&lt;/p&gt;
&lt;h4 id=&quot;the-re-release-day&quot;&gt;The re-release day&lt;/h4&gt;
&lt;p&gt;There is a catch. The October headers route &lt;code&gt;smart_str&lt;/code&gt; growth through a new engine function, &lt;code&gt;smart_str_ferealloc()&lt;/code&gt;, from inline header code. An extension compiled against 8.4.27 headers imports that symbol even if it never mentions it, and 8.4.26 doesn't export it. My release workflows built prebuilt binaries against the newest patch of each PHP minor, which had always been the safe choice.&lt;/p&gt;
&lt;p&gt;On October 26 that meant mdparser 0.8.1, phpser 0.10.0, fastjson 0.10.1, php_clickhouse 0.13.1, and phonetic 0.4.5 all shipped Linux and macOS binaries that died with &lt;code&gt;undefined symbol: smart_str_ferealloc&lt;/code&gt; on every PHP 8.4 before 8.4.27 and every 8.5 before 8.5.12. Source builds were fine, because they compile against the PHP they load into. All five were re-released the same day as mdparser 0.8.2, phpser 0.10.1, fastjson 0.10.2, php_clickhouse 0.13.2, and phonetic 0.4.6, and fastchart 1.8.1 picked up the same change before it could break. Their prebuilts now compile against the oldest patch of each minor (8.4.1, since 8.4.0 was never released, and 8.5.0), and each binary has to load on both the oldest and the newest patch before anything uploads.&lt;/p&gt;
&lt;p&gt;That fix has a cost, and the phpser changelog states it: a binary built against 8.4.1 headers doesn't have the &lt;code&gt;memory_limit&lt;/code&gt; stop. To get it, you build from source on a patched PHP.&lt;/p&gt;
&lt;h4 id=&quot;refusing-vulnerable-php-releases&quot;&gt;Refusing vulnerable PHP releases&lt;/h4&gt;
&lt;p&gt;The longer-term answer is the opposite trade. pdo_duckdb 0.9.0 makes it today: its prebuilt binaries are built against the patched headers and require PHP 8.2.35, 8.3.36, 8.4.27, or 8.5.12 or newer. Load one on 8.4.26 and the process stops at the first call:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;php: symbol lookup error: /ext/pdo_duckdb.so: undefined symbol: smart_str_ferealloc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That message is honest but unfriendly, and it arrives at runtime rather than at startup. The next releases of pdo_duckdb, phpser, and mdparser fix that with a startup check. On PHP 8.3 and newer, the module compares the running patch with the one it was compiled against and refuses to start on an older one, naming both versions. This is mdparser's:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (php_version_id() &amp;lt; PHP_VERSION_ID) {
    zend_error(E_CORE_WARNING,
        &quot;mdparser was built for PHP %s and cannot run on PHP %s; &quot;
        &quot;install a build for this PHP version or build from source&quot;,
        PHP_VERSION, php_version());
    return FAILURE;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that check in place, pdo_duckdb keeps the binaries it already builds against the patched headers, and phpser and mdparser can move theirs back to the patched headers and regain the &lt;code&gt;memory_limit&lt;/code&gt; stop. A server still on 8.4.26 gets a clear startup warning instead of a crash on the first request, and with a critical CVE fixed in 8.4.27 it should move anyway.&lt;/p&gt;
&lt;h3 id=&quot;pdo-duckdb-0-9-0-duckdb-2-0&quot;&gt;pdo_duckdb 0.9.0: DuckDB 2.0&lt;/h3&gt;
&lt;p&gt;DuckDB 2.0.0 went GA on October 21, and pdo_duckdb 0.9.0, out today, bundles it in every prebuilt binary. 0.8.0 had already built against the 2.0 previews. Source builds still work against DuckDB 1.5.3 and newer; &lt;code&gt;configure&lt;/code&gt; detects 2.0 from &lt;code&gt;duckdb_v2.h&lt;/code&gt;, because the 2.0 headers still report C API version 1.5.6, and it refuses to build when 2.0 headers meet an older &lt;code&gt;libduckdb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The upgrade is not transparent, and the README has a full section on it. The one most likely to bite is the file format. DuckDB 2.0 writes new files at storage version 69, and DuckDB 1.5 reads only 64 to 68. If a 1.5 process has to read the file too, ask for the older format when you open it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$db = new PDO('duckdb:/data/shared.duckdb;storage_compatibility_version=v1.5.6');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the 0.9.0 binary, a file opened that way reports &lt;code&gt;v1.5.0+&lt;/code&gt; in &lt;code&gt;duckdb_databases()&lt;/code&gt;, and a default one reports &lt;code&gt;v2.0.0+&lt;/code&gt;. Existing 1.5 files keep their format after 2.0 writes to them. The rest is DuckDB's own behaviour: a failed statement now invalidates the whole transaction, integer division by zero throws, single-arrow lambdas are rejected, and the new PEG parser is the default. That last one has a sharp edge for parameter binding: &lt;code&gt;$1_0&lt;/code&gt; means parameter 10 under PEG and parameter 1 under the legacy parser. Each change has a setting that restores 1.5 behaviour.&lt;/p&gt;
&lt;p&gt;0.9.0 also adds &lt;code&gt;PDO::DUCKDB_ATTR_NATIVE_STRUCTURES&lt;/code&gt;, an opt-in that fetches LIST, ARRAY, STRUCT, TUPLE, MAP, UNION, JSON, and VARIANT values as lossless PHP structures instead of canonical strings, with exact numerics, binary keys, and SQL &lt;code&gt;NULL&lt;/code&gt; kept distinct from VARIANT null. VARIANT works top-level and nested, and the appender accepts PHP arrays and objects for it.&lt;/p&gt;
&lt;p&gt;One fix in 0.9.0 matters even if you never touch 2.0: two PDO connections to the same database file could lose writes and see stale reads. Equivalent paths now share one DuckDB instance while transactions stay connection-local.&lt;/p&gt;
&lt;h3 id=&quot;performance-with-the-hardware-attached&quot;&gt;Performance, with the hardware attached&lt;/h3&gt;
&lt;p&gt;Most of the speed work came before the scans. Apart from the nameparser row, which I re-ran for this post, these are the numbers from each project's changelog with the conditions it states.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Project &lt;/th&gt;&lt;th&gt;Change &lt;/th&gt;&lt;th&gt;Measured on&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;nameparser 1.6.0 &lt;/td&gt;&lt;td&gt;16 KB hostile row: 17.4 s to 0.064 s &lt;/td&gt;&lt;td&gt;i9-13950HX, PHP 8.4.27 (my rerun of 1.5.0 vs 1.7.0)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;phpser 0.6.0 &lt;/td&gt;&lt;td&gt;&lt;code&gt;range(0, 9999)&lt;/code&gt; encodes to 7 bytes; encode 57% faster, decode 66% faster &lt;/td&gt;&lt;td&gt;aarch64&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;phpser 0.7.0 &lt;/td&gt;&lt;td&gt;Signed decode 53 to 85% faster; 1,000-row rowset decodes in 429 KB instead of 645 KB &lt;/td&gt;&lt;td&gt;aarch64&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;fastjson 0.9.0 &lt;/td&gt;&lt;td&gt;&lt;code&gt;fastjson_validate()&lt;/code&gt; costs 0.47 to 0.90x a full parse, down from 1.1 to 1.9x &lt;/td&gt;&lt;td&gt;Neoverse-N1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;fastjson 0.9.0 to 0.10.0 &lt;/td&gt;&lt;td&gt;Key-heavy decode peak memory about 15% lower; file decode peak 9 to 33% lower &lt;/td&gt;&lt;td&gt;Neoverse-N1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;fast_uuid 0.8.0 &lt;/td&gt;&lt;td&gt;&lt;code&gt;uuid_v4()&lt;/code&gt; 76 to 42 ns; &lt;code&gt;uuid_v4_bin_batch(100)&lt;/code&gt; 61 to 27 ns per UUID; &lt;code&gt;uuid_v7_bin_batch()&lt;/code&gt; 25.9 to 16.6 ns &lt;/td&gt;&lt;td&gt;Neoverse-N1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;mdparser 0.6.2 &lt;/td&gt;&lt;td&gt;&lt;code&gt;toAst()&lt;/code&gt; 5 to 9% faster; retained AST memory 47% lower on softbreak-dense input; code-heavy &lt;code&gt;toXml()&lt;/code&gt; output 49% smaller in memory &lt;/td&gt;&lt;td&gt;aarch64, PHP 8.4.25&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;php_clickhouse 0.12.0 &lt;/td&gt;&lt;td&gt;Temporal, string, and IP inserts about 19% faster; IPv4 selects about 12% &lt;/td&gt;&lt;td&gt;200,000-row probes&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The phpser integer-run encoding is the one I'd point at. A run with a constant step is stored as a start, a step, and a count, so &lt;code&gt;range(0, 9999)&lt;/code&gt; encodes to 7 bytes and &lt;code&gt;range(0, 999999)&lt;/code&gt; to 8. Native &lt;code&gt;serialize()&lt;/code&gt; needs 137,790 bytes for the first one.&lt;/p&gt;
&lt;p&gt;fast_uuid's speedup comes from refilling its random buffer with a userspace AES-256-CTR generator with fast key erasure, instead of a &lt;code&gt;getrandom()&lt;/code&gt; call per 8 KiB, on CPUs with AES instructions. It has one caveat, and the changelog states it: clones of one VM snapshot can repeat up to 64 KiB of output per thread.&lt;/p&gt;
&lt;h3 id=&quot;breaking-changes-worth-reading-before-you-upgrade&quot;&gt;Breaking changes worth reading before you upgrade&lt;/h3&gt;
&lt;p&gt;Most of these releases are drop-in. These are not:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;php_clickhouse 0.13.0: whole-number Decimal inserts scale like the server's (above).&lt;/li&gt;
&lt;li&gt;mdparser 0.8.0: heading IDs carry a &lt;code&gt;user-content-&lt;/code&gt; prefix.&lt;/li&gt;
&lt;li&gt;phpser 0.10.0: Closures and other unserializable objects throw instead of quietly encoding &lt;code&gt;NULL&lt;/code&gt;. Check what you put in &lt;code&gt;$_SESSION&lt;/code&gt; before upgrading. New wire tags since 0.7.0 mean readers must upgrade before writers.&lt;/li&gt;
&lt;li&gt;php_excel 2.9.0 and 2.9.1: dates use PHP's timezone rather than the OS one, and text is UTF-8 regardless of the process locale. Apps that relied on the locale to pass non-UTF-8 bytes through will see different output. 2.7.0 also rejects a font from a different &lt;code&gt;ExcelBook&lt;/code&gt; in &lt;code&gt;addText()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;fastchart 1.8.0: heavy SVG that used to render slowly may now throw &lt;code&gt;ValueError&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;phonetic 0.4.4: oversized BMPM and Daitch-Mokotoff work throws a catchable &lt;code&gt;ValueError&lt;/code&gt; rather than ending the request.&lt;/li&gt;
&lt;li&gt;statgrab 2.2.2: &lt;code&gt;Statgrab::ERROR_UNSUPPORTED&lt;/code&gt; is 44, matching libstatgrab; it was 41.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On the feature side, phpser 0.8.0 and 0.9.0 added signed sessions (&lt;code&gt;phpser.session_key&lt;/code&gt;, HMAC-SHA256), an APCu serializer (&lt;code&gt;apc.serializer=phpser&lt;/code&gt;), keyed payloads, and an exported C API. nameparser 1.4.0 understands joint salutations, so &lt;code&gt;Mr. and Mrs. Brad Smith&lt;/code&gt; keeps &lt;code&gt;Brad&lt;/code&gt; as the first name, with new &lt;code&gt;isJoint()&lt;/code&gt;, &lt;code&gt;getSalutations()&lt;/code&gt;, and &lt;code&gt;getPartner()&lt;/code&gt;, and 1.4.1 raised first-and-last accuracy on 30,000 NPPES provider rows from 95.94% to 97.18%. statgrab 2.3.1 fixes a heap overflow in the bundled libstatgrab's Linux command-line handling that the scan didn't cover.&lt;/p&gt;
&lt;h3 id=&quot;what-a-scan-is-worth&quot;&gt;What a scan is worth&lt;/h3&gt;
&lt;p&gt;None of these projects were neglected before October. Each had a test suite and documentation that said where its limits were. The scans still found an allowlist that didn't reach a nested call, a Decimal that came back ten thousand times too small, and a 16 KB name that took 17 seconds. Two of the most consequential findings sat in code I had already called linear or already tested under ASan, and for the Decimal bug the ASan testing is exactly what hid it.&lt;/p&gt;
&lt;p&gt;The fixes are released and listed above. Upgrade pdo_duckdb if you are moving to DuckDB 2.0, php_clickhouse if you talk to a server you don't control, and PHP itself to 8.4.27 or 8.5.12 whatever else you do.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32689/seventeen-seconds-to-parse-one-name-a-security-scan-across-eleven-php-projects</guid>
<pubDate>Tue, 27 Oct 2026 13:32:42 +0000</pubDate>
</item>
<item>
<title>Mastering React Performance: Optimizations That Actually Matter 🚀</title>
<link>https://coderlegion.com/32367/mastering-react-performance-optimizations-that-actually-matter</link>
<description>&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;React is a powerful JavaScript library, but as applications grow, performance bottlenecks can arise. Unnecessary re-renders, inefficient state management, and large bundle sizes can lead to slow applications.&lt;/p&gt;
&lt;p&gt;This guide covers practical performance optimizations that actually matter. Whether you’re working on a small project or a large-scale application, these best practices will help you write faster, scalable, and efficient React applications.&lt;/p&gt;
&lt;p&gt;By the end of this blog, you’ll learn:&lt;br&gt;
✅ Why React apps slow down&lt;br&gt;
✅ Best practices for optimizing re-renders&lt;br&gt;
✅ How to efficiently manage state&lt;br&gt;
✅ How to optimize large lists&lt;br&gt;
✅ lazy loading for faster load times&lt;/p&gt;
&lt;p&gt;Let’s dive in! 🚀&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;1-understanding-react-s-rendering-behavior&quot;&gt;1️⃣ Understanding React’s Rendering Behavior&lt;/h4&gt;
&lt;p&gt;Before optimizing, it’s crucial to understand how React renders components.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;📌 React re-renders a component when:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;State changes in that component.&lt;/li&gt;
&lt;li&gt;Props change from the parent component.&lt;/li&gt;
&lt;li&gt;A parent re-renders, causing child components to re-render.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;🔍 How to detect unnecessary re-renders?&lt;/strong&gt;&lt;br&gt;
Use &lt;a rel=&quot;nofollow&quot; href=&quot;https://legacy.reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html&quot;&gt;React DevTools Profiler&lt;/a&gt; to analyze component renders.&lt;/p&gt;
&lt;p&gt;✅ Solution: Memoization &amp;amp; Avoiding Unnecessary Renders&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-optimize-component-re-renders-with-react-memo&quot;&gt;2️⃣ Optimize Component Re-Renders with &lt;code&gt;React.memo&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;By default, React re-renders a component &lt;strong&gt;when its parent renders&lt;/strong&gt;, even if props haven’t changed.&lt;/p&gt;
&lt;p&gt;🚀 Solution: Use &lt;code&gt;React.memo()&lt;/code&gt; to prevent unnecessary renders&lt;/p&gt;
&lt;p&gt;🔴 Without &lt;code&gt;React.memo&lt;/code&gt; (Unnecessary re-renders)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { useState } from &quot;react&quot;;

const MovieDetails = ({ title, director }) =&amp;gt; {
  console.log(&quot;🎬 MovieDetails component re-rendered&quot;);
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;{title}&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;Directed by: {director}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const App = () =&amp;gt; {
  const [count, setCount] = useState(0);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;React.memo Example&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;Click Me ({count})&amp;lt;/button&amp;gt;
      &amp;lt;MovieDetails title=&quot;Inception&quot; director=&quot;Christopher Nolan&quot; /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;👉 Here, &lt;code&gt;MovieDetails&lt;/code&gt; &lt;strong&gt;re-renders every time&lt;/strong&gt; the parent renders, even if props hasn't changed.&lt;/p&gt;
&lt;p&gt;🟢 Optimizing with &lt;code&gt;React.memo&lt;/code&gt;&lt;br&gt;
To prevent unnecessary re-renders, wrap &lt;code&gt;MovieDetails&lt;/code&gt; with &lt;code&gt;React.memo()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const MovieDetails = React.memo(({ title, director }) =&amp;gt; {
  console.log(&quot;🎬 MovieDetails component re-rendered&quot;);
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;{title}&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;Directed by: {director}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;👉 Now, &lt;code&gt;MovieDetails&lt;/code&gt; will only re-render if its props (title or director) change.&lt;/p&gt;
&lt;p&gt;Clicking the button will no longer trigger a re-render of &lt;code&gt;MovieDetails&lt;/code&gt; unless new props are passed.&lt;/p&gt;
&lt;p&gt;📌 When to use &lt;code&gt;React.memo&lt;/code&gt;?&lt;br&gt;
✅ The component receives static or rarely changing props (e.g., user profile, movie info).&lt;br&gt;
✅ The component is expensive to render (e.g., complex UI elements like charts).&lt;br&gt;
✅ The parent re-renders frequently, but child props remain the same.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-minimize-re-renders-with-derived-state&quot;&gt;3️⃣ Minimize Re-Renders with Derived State&lt;/h4&gt;
&lt;p&gt;Unnecessary state updates can cause frequent re-renders, slowing down your React app. Instead of storing derived state, compute values when needed.&lt;/p&gt;
&lt;p&gt;🔴 Problem: Storing Derived State in useState (Causes Extra Re-Renders)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const [items, setItems] = useState([...]); 
const [filteredItems, setFilteredItems] = useState([]);

useEffect(() =&amp;gt; {
  setFilteredItems(items.filter(item =&amp;gt; item.active));
}, [items]); // Extra re-render every time `items` change
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;📌 Issue:&lt;br&gt;
Every update to items triggers an unnecessary state update for &lt;code&gt;filteredItems&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This causes an extra re-render that could have been avoided.&lt;/p&gt;
&lt;p&gt;🟢 Solution: Use Derived State Instead of Storing It&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const filteredItems = items.filter(item =&amp;gt; item.active);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;✅ Now, &lt;code&gt;filteredItems&lt;/code&gt; is computed only when needed, avoiding extra state updates and re-renders.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-optimize-function-props-with-usecallback&quot;&gt;4️⃣ Optimize Function Props with &lt;code&gt;useCallback&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Passing new function instances as props causes child components to unnecessarily re-render.&lt;/p&gt;
&lt;p&gt;🔴 Problem: Without &lt;code&gt;useCallback&lt;/code&gt; (Function re-creates on every render)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const ParentComponent = () =&amp;gt; {
  const handleClick = () =&amp;gt; {
    console.log(&quot;Button clicked&quot;);
  };

  return &amp;lt;ButtonComponent handleClick={handleClick} /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;👉 Even if &lt;code&gt;handleClick&lt;/code&gt; doesn’t change, it gets re-created every time &lt;code&gt;ParentComponent&lt;/code&gt; renders.&lt;/p&gt;
&lt;p&gt;🟢 Solution: Use &lt;code&gt;useCallback&lt;/code&gt; to memoize the function&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const handleClick = useCallback(() =&amp;gt; {
  console.log(&quot;Button clicked&quot;);
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ Now, &lt;code&gt;handleClick&lt;/code&gt; retains the &lt;strong&gt;same reference&lt;/strong&gt;, preventing unnecessary re-renders.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;5-optimize-expensive-computations-with-usememo&quot;&gt;5️⃣ Optimize Expensive Computations with &lt;code&gt;useMemo&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;If a component performs heavy calculations on every render, it can slow down the UI.&lt;/p&gt;
&lt;p&gt;🔴 Problem: Without &lt;code&gt;useMemo&lt;/code&gt; (Expensive re-computation)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const heavyComputation = (num) =&amp;gt; {
  console.log(&quot;Expensive calculation running...&quot;);
  return num * 2;
};

const Component = ({ number }) =&amp;gt; {
  const result = heavyComputation(number); // Runs on every render
  return &amp;lt;p&amp;gt;Result: {result}&amp;lt;/p&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;🟢 Solution: Use &lt;code&gt;useMemo&lt;/code&gt; to cache expensive computations&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const result = useMemo(() =&amp;gt; heavyComputation(number), [number]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ &lt;code&gt;heavyComputation()&lt;/code&gt; only re-runs when &lt;code&gt;number&lt;/code&gt; changes.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;6-optimize-lists-amp-large-data-rendering&quot;&gt;6️⃣ Optimize Lists &amp;amp; Large Data Rendering&lt;/h4&gt;
&lt;p&gt;🔍 Problem: Rendering large lists slows down UI&lt;br&gt;
Solution: Use Virtualization with &lt;code&gt;react-window&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;🔴 Without Virtualization (Slow performance)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const List = ({ items }) =&amp;gt; {
  return (
    &amp;lt;ul&amp;gt;
      {items.map((item) =&amp;gt; (
        &amp;lt;li key={item.id}&amp;gt;{item.name}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;🟢 With Virtualization (react-window)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { FixedSizeList as List } from &quot;react-window&quot;;

const VirtualizedList = ({ items }) =&amp;gt; (
  &amp;lt;List height={500} itemCount={items.length} itemSize={35} width={300}&amp;gt;
    {({ index, style }) =&amp;gt; (
      &amp;lt;div style={style}&amp;gt;{items[index].name}&amp;lt;/div&amp;gt;
    )}
  &amp;lt;/List&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ Only visible items are rendered, improving performance significantly.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;7-reduce-bundle-size-with-code-splitting-amp-lazy-loading&quot;&gt;7️⃣ Reduce Bundle Size with Code Splitting &amp;amp; Lazy Loading&lt;/h4&gt;
&lt;p&gt;Large JavaScript bundles slow down initial page load.&lt;/p&gt;
&lt;p&gt;✅ Solution: Use &lt;code&gt;React.lazy()&lt;/code&gt; for dynamic imports&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const HeavyComponent = React.lazy(() =&amp;gt; import(&quot;./HeavyComponent&quot;));

const App = () =&amp;gt; (
  &amp;lt;Suspense fallback={&amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;}&amp;gt;
    &amp;lt;HeavyComponent /&amp;gt;
  &amp;lt;/Suspense&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;👉 Components load only when needed, reducing bundle size.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;conclusion-build-fast-amp-scalable-react-apps&quot;&gt;Conclusion: Build Fast &amp;amp; Scalable React Apps&lt;/h4&gt;
&lt;p&gt;Optimizing React apps is crucial for fast UI interactions and smooth performance. By implementing:&lt;br&gt;
✅ Memoization (&lt;code&gt;React.memo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, &lt;code&gt;useMemo&lt;/code&gt;)&lt;br&gt;
✅ Efficient state management&lt;br&gt;
✅ Virtualization for large lists&lt;br&gt;
✅ Lazy loading for faster page loads&lt;/p&gt;
&lt;p&gt;You’ll significantly improve performance! 🚀&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;💡 What performance tricks do you use in React?&lt;/strong&gt; Drop a comment below! 👇&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32367/mastering-react-performance-optimizations-that-actually-matter</guid>
<pubDate>Sun, 25 Oct 2026 05:57:59 +0000</pubDate>
</item>
<item>
<title>Finding the slow part of a WooCommerce page with Query Monitor</title>
<link>https://coderlegion.com/32348/finding-the-slow-part-of-a-woocommerce-page-with-query-monitor</link>
<description>&lt;p&gt;Query Monitor is a free WordPress plugin by John Blackbourn. It adds a panel to the admin toolbar that shows what one page load did: database queries, hooks, HTTP calls, PHP errors and the template that rendered the page. On a WooCommerce store it answers the usual question, &quot;which plugin makes the product page slow&quot;, with numbers instead of guesses.&lt;/p&gt;
&lt;h3 id=&quot;install-it-on-staging-first&quot;&gt;Install it on staging first&lt;/h3&gt;
&lt;p&gt;Query Monitor shows its panel to administrators only, so customers on a live store never see it. It still does its work on every request you make while logged in, which is why staging is the better home for it. Install it from the plugin directory and activate it. The toolbar then shows four figures for the current page: generation time, peak memory, database query time and the number of queries.&lt;/p&gt;
&lt;h3 id=&quot;read-the-queries-by-component&quot;&gt;Read the queries by component&lt;/h3&gt;
&lt;p&gt;Open the panel and go to Queries by Component. Every query is attributed to the plugin, theme or core file that made it, with a count and a total time. Sorting by time usually points at one or two plugins.&lt;/p&gt;
&lt;p&gt;Duplicate Queries is the next view to open. The same query repeated dozens of times in one request often comes from a loop that reads product meta one product at a time.&lt;/p&gt;
&lt;p&gt;Slow Queries lists every query that took longer than 0.05 seconds. That threshold is the QM_DB_EXPENSIVE constant, and you can lower it in wp-config.php when the whole page is slow but no single query stands out.&lt;/p&gt;
&lt;h3 id=&quot;check-outgoing-http-calls&quot;&gt;Check outgoing HTTP calls&lt;/h3&gt;
&lt;p&gt;The HTTP API Calls panel lists the requests a page sent to other servers while it was being built. Licence checks, currency rates and shipping quotes are the usual suspects. A plugin that calls an external API on every product page adds that server's response time to yours. For each call the panel shows the URL, the response code and how long it took.&lt;/p&gt;
&lt;h3 id=&quot;time-your-own-code&quot;&gt;Time your own code&lt;/h3&gt;
&lt;p&gt;Query Monitor also has a small profiling API. Wrap a block of code in two actions and it shows up in the Timings panel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;do_action( 'qm/start', 'shipping_estimates' );
// the code you want to measure
do_action( 'qm/stop', 'shipping_estimates' );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a value you want to inspect, &lt;code&gt;do_action( 'qm/debug', $variable );&lt;/code&gt; sends it to the Logs panel instead of the page output.&lt;/p&gt;
&lt;h3 id=&quot;look-at-a-page-as-a-guest&quot;&gt;Look at a page as a guest&lt;/h3&gt;
&lt;p&gt;Some slowness only appears for logged-out visitors, for example when a cache plugin treats them differently. The Query Monitor settings can set an authentication cookie, and with it the panel keeps working in the same browser after you log out.&lt;/p&gt;
&lt;h3 id=&quot;when-you-are-done&quot;&gt;When you are done&lt;/h3&gt;
&lt;p&gt;Deactivate the plugin once the slow part is found. It is light, but there is no reason to keep it running on a store that nobody is debugging.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/32348/finding-the-slow-part-of-a-woocommerce-page-with-query-monitor</guid>
<pubDate>Sat, 24 Oct 2026 22:32:40 +0000</pubDate>
</item>
<item>
<title>Shipping a 4K Wallpaper App Without Running Out of Memory: ImageIO Downsampling and Core Image</title>
<link>https://coderlegion.com/32271/shipping-4k-wallpaper-app-without-running-out-memory-imageio-downsampling-and-core-image</link>
<description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bestlatestapps.com/blog/shipping-a-4k-wallpaper-app-without-running-out-of-memory/&quot;&gt;https://bestlatestapps.com/blog/shipping-a-4k-wallpaper-app-without-running-out-of-memory/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Decoded-image maths, thumbnails that don't decode the full file, a blur editor that keeps edges clean, saving with the minimum permission, and why your app can't set the wallpaper itself&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bestlatestapps.com/assets/blog/shipping-a-4k-wallpaper-app-without-running-out-of-memory/wallpapers-hd-icon-512.jpg&quot; alt=&quot;Wallpapers HD: Exotic 4K app icon&quot;&gt;&lt;/p&gt;
&lt;p&gt;A wallpaper app looks like the simplest kind of app there is: a grid of pictures, tap one, save it. Then you load forty 4K images into a scrolling grid on an older iPhone, and the system terminates your app for using too much memory.&lt;/p&gt;
&lt;p&gt;My app &lt;strong&gt;Wallpapers HD: Exotic 4K&lt;/strong&gt; lists &quot;fresh 4K HD wallpapers&quot;, &quot;parallax-ready backgrounds that look sharp on every iPhone display&quot;, a Create mode to &quot;edit photos and add blur effects&quot;, categories from Nature and Anime to Space and Minimalism, search, and saving so you can set an image as your Lock Screen or Home Screen. It's been on the App Store since December 2022.&lt;/p&gt;
&lt;p&gt;Every one of those features runs into the same physical fact: &lt;strong&gt;a decoded image costs width × height × 4 bytes, no matter how small the file is.&lt;/strong&gt; This article covers how I'd build a wallpaper app around that fact. That means downsampled thumbnails with ImageIO, full-resolution images only when needed, a Core Image blur editor, saving with add-only Photos access, and an honest explanation of the &quot;set as wallpaper&quot; step. API names and availability are checked against Apple's documentation, and this is the approach I'd use rather than the app's exact source.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-memory-maths-everyone-forgets&quot;&gt;1. The memory maths everyone forgets&lt;/h3&gt;
&lt;p&gt;A 4K image (3840 × 2160) might be a 2 MB JPEG on disk. Decoded for display, it's:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;3840 × 2160 pixels × 4 bytes (RGBA) ≈ 33 MB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A grid showing 12 of those at once is around 400 MB of bitmap memory before you've scrolled anywhere. Yet a grid cell might be 120 × 260 points, about 360 × 780 pixels on a 3× display, which needs roughly &lt;strong&gt;1.1 MB&lt;/strong&gt; decoded.&lt;/p&gt;
&lt;p&gt;So the rule is: &lt;strong&gt;never decode a bigger image than you're going to display.&lt;/strong&gt; &lt;code&gt;UIImage(data:)&lt;/code&gt; decodes at full size. ImageIO can decode straight to the size you need.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-downsampling-with-imageio&quot;&gt;2. Downsampling with ImageIO&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;CGImageSourceCreateThumbnailAtIndex&lt;/code&gt; (iOS 4+) creates a smaller image directly from the compressed data, without ever holding the full-size bitmap in memory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import ImageIO
import UIKit

enum Downsampler {
    /// Decodes `data` at most `maxPixelSize` on its longest side.
    static func image(from data: Data, maxPixelSize: CGFloat, scale: CGFloat) -&amp;gt; UIImage? {
        let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
        guard let source = CGImageSourceCreateWithData(data as CFData, sourceOptions) else { return nil }

        let options = [
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceShouldCacheImmediately: true,      // decode now, off the main thread
            kCGImageSourceCreateThumbnailWithTransform: true, // respect EXIF orientation
            kCGImageSourceThumbnailMaxPixelSize: maxPixelSize
        ] as CFDictionary

        guard let cg = CGImageSourceCreateThumbnailAtIndex(source, 0, options) else { return nil }
        return UIImage(cgImage: cg, scale: scale, orientation: .up)
    }

    /// Same, from a file URL: avoids loading the whole file into memory first.
    static func image(at url: URL, maxPixelSize: CGFloat, scale: CGFloat) -&amp;gt; UIImage? {
        let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
        guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else { return nil }
        let options = [
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceShouldCacheImmediately: true,
            kCGImageSourceCreateThumbnailWithTransform: true,
            kCGImageSourceThumbnailMaxPixelSize: maxPixelSize
        ] as CFDictionary
        return CGImageSourceCreateThumbnailAtIndex(source, 0, options)
            .map { UIImage(cgImage: $0, scale: scale, orientation: .up) }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two options do the important work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;kCGImageSourceShouldCache: false&lt;/code&gt; on the source&lt;/strong&gt; stops ImageIO from caching a full-size decode as a side effect.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;kCGImageSourceShouldCacheImmediately: true&lt;/code&gt; on the thumbnail&lt;/strong&gt; forces decoding &lt;em&gt;now&lt;/em&gt;, which you'll do on a background task, instead of lazily on the main thread during the first draw, where it causes scroll hitches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On iOS 15+, &lt;code&gt;UIImage.preparingThumbnail(of:)&lt;/code&gt; is a convenient alternative when you already have a &lt;code&gt;UIImage&lt;/code&gt;. But a &lt;code&gt;UIImage&lt;/code&gt; made from data hasn't necessarily been decoded yet, and ImageIO gives you the most control.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-a-grid-that-stays-smooth&quot;&gt;3. A grid that stays smooth&lt;/h3&gt;
&lt;p&gt;Better still: &lt;strong&gt;don't download 4K images for the grid at all.&lt;/strong&gt; Have your image host serve a small rendition for browsing and the full file only for the detail screen. Then downsample whatever arrives to the cell's pixel size anyway, because servers change.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import SwiftUI

actor ThumbnailCache {
    static let shared = ThumbnailCache()
    private let cache = NSCache&amp;lt;NSURL, UIImage&amp;gt;()
    init() { cache.totalCostLimit = 80 * 1024 * 1024 }    // ~80 MB of decoded thumbnails

    func image(for url: URL, pixelSize: CGFloat, scale: CGFloat) async -&amp;gt; UIImage? {
        if let hit = cache.object(forKey: url as NSURL) { return hit }
        guard let (data, _) = try? await URLSession.shared.data(from: url),
              let image = Downsampler.image(from: data, maxPixelSize: pixelSize, scale: scale)
        else { return nil }
        let cost = Int(image.size.width * image.scale * image.size.height * image.scale * 4)
        cache.setObject(image, forKey: url as NSURL, cost: cost)
        return image
    }
}

struct WallpaperCell: View {
    let thumbnailURL: URL
    @Environment(\.displayScale) private var scale
    @State private var image: UIImage?

    var body: some View {
        GeometryReader { geo in
            ZStack {
                Color(.secondarySystemBackground)
                if let image { Image(uiImage: image).resizable().scaledToFill() }
            }
            .frame(width: geo.size.width, height: geo.size.height)
            .clipped()
            .task(id: thumbnailURL) {
                let px = max(geo.size.width, geo.size.height) * scale
                image = await ThumbnailCache.shared.image(for: thumbnailURL, pixelSize: px, scale: scale)
            }
        }
        .aspectRatio(9.0 / 19.5, contentMode: .fit)
        .clipShape(RoundedRectangle(cornerRadius: 12))
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The details that matter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;NSCache&lt;/code&gt; with a cost limit&lt;/strong&gt; sized in &lt;em&gt;decoded bytes&lt;/em&gt;, not image count, so the system can also evict under memory pressure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;.task(id:)&lt;/code&gt;&lt;/strong&gt; cancels the load when a cell scrolls off-screen before finishing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@Environment(\.displayScale)&lt;/code&gt;&lt;/strong&gt;, not &lt;code&gt;UIScreen.main.scale&lt;/code&gt;. Apple's docs now mark &lt;code&gt;UIScreen.main&lt;/code&gt; as deprecated in iOS 26. Read the scale and screen size from the view's environment or window scene instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Why not just &lt;code&gt;AsyncImage&lt;/code&gt; (iOS 15+)? It's fine for small images, but it decodes whatever it downloads at full size and has no memory-cost cache. For a wallpaper grid, owning the pipeline pays off.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-the-detail-screen-full-resolution-exactly-once&quot;&gt;4. The detail screen: full resolution, exactly once&lt;/h3&gt;
&lt;p&gt;When the user opens a wallpaper, they deserve every pixel, but only one full-size image should be alive at a time. Download to a file (it streams to disk instead of RAM) and decode to the &lt;strong&gt;device's native screen size&lt;/strong&gt; rather than the source's 4K:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import UIKit

enum FullImageLoader {
    /// Downloads to a temporary file, then decodes at the screen's native pixel size.
    static func load(_ url: URL, nativeScreenSize: CGSize, scale: CGFloat) async throws -&amp;gt; (UIImage, URL) {
        let (tempURL, _) = try await URLSession.shared.download(from: url)
        // Move out of the system's temporary location before it's cleaned up.
        let kept = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent)
        try? FileManager.default.removeItem(at: kept)
        try FileManager.default.moveItem(at: tempURL, to: kept)

        let longest = max(nativeScreenSize.width, nativeScreenSize.height)
        guard let image = Downsampler.image(at: kept, maxPixelSize: longest, scale: scale) else {
            throw CocoaError(.fileReadCorruptFile)
        }
        return (image, kept)    // keep the file: saving uses the original bytes
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;UIScreen.nativeBounds&lt;/code&gt; (iOS 8+) gives the screen's size in physical pixels. Get the screen from the current window scene (&lt;code&gt;view.window?.windowScene?.screen&lt;/code&gt;) rather than &lt;code&gt;UIScreen.main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What about &quot;parallax-ready&quot;?&lt;/strong&gt; iOS can apply motion and depth effects to wallpapers, and the system crops and zooms to fit the device. The practical rule: supply images at &lt;em&gt;least&lt;/em&gt; as large as the native screen, in portrait, with the subject away from the edges, so the system has room to crop and move. That's why a 4K source is useful even though you decode the preview smaller. You &lt;em&gt;save&lt;/em&gt; the original, not the preview.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-create-mode-blur-that-keeps-clean-edges&quot;&gt;5. Create mode: blur that keeps clean edges&lt;/h3&gt;
&lt;p&gt;&quot;Create your own wallpapers — edit photos and add blur effects.&quot; Core Image's Gaussian blur is the right tool, with one classic gotcha: blurring pulls in transparent pixels from outside the image, which gives you a dark, soft border. Clamp the image to its extent first, then crop back:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import CoreImage
import CoreImage.CIFilterBuiltins
import UIKit

final class BlurEditor {
    private let context = CIContext(options: [.useSoftwareRenderer: false])
    private let source: CIImage

    init?(image: UIImage) {
        guard let ci = CIImage(image: image) else { return nil }
        source = ci
    }

    /// radius in pixels of the *output*; scale it if you preview at a smaller size.
    func render(radius: Double, dim: Double = 0) -&amp;gt; UIImage? {
        let blur = CIFilter.gaussianBlur()
        blur.inputImage = source.clampedToExtent()     // no dark edges
        blur.radius = Float(radius)
        var output = blur.outputImage?.cropped(to: source.extent) ?? source

        if dim &amp;gt; 0 {                                   // darken for legible clock and widgets
            let controls = CIFilter.colorControls()
            controls.inputImage = output
            controls.brightness = Float(-dim)
            output = controls.outputImage ?? output
        }

        guard let cg = context.createCGImage(output, from: source.extent) else { return nil }
        return UIImage(cgImage: cg)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Performance tips for a live slider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Preview on the downsampled image&lt;/strong&gt; and render the full-resolution output only when the user taps Save. Blur cost grows with the pixel count &lt;em&gt;and&lt;/em&gt; the radius.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scale the radius&lt;/strong&gt; between preview and export. A 20-pixel blur on a 1,290-pixel-wide preview is about a 60-pixel blur on a 3,840-pixel export.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reuse one &lt;code&gt;CIContext&lt;/code&gt;.&lt;/strong&gt; Creating a context per slider tick is the slowest possible way to use Core Image.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The optional &lt;code&gt;dim&lt;/code&gt; parameter is a small design touch: a slightly darker, blurred background keeps the Lock Screen clock and widgets readable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-saving-with-the-minimum-permission&quot;&gt;6. Saving with the minimum permission&lt;/h3&gt;
&lt;p&gt;Saving an image to Photos doesn't require read access to the user's library. iOS has an &lt;strong&gt;add-only&lt;/strong&gt; access level for exactly this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Photos
import UIKit

enum PhotoSaver {
    enum SaveError: Error { case denied }

    /// Saves the original file bytes (not a re-encoded UIImage) to Photos.
    static func saveOriginal(fileURL: URL) async throws {
        let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly)   // iOS 14+
        guard status == .authorized || status == .limited else { throw SaveError.denied }

        try await PHPhotoLibrary.shared().performChanges {
            let request = PHAssetCreationRequest.forAsset()
            request.addResource(with: .photo, fileURL: fileURL, options: nil)
        }
    }

    /// For edited images from Create mode.
    static func save(_ image: UIImage) async throws {
        let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly)
        guard status == .authorized || status == .limited else { throw SaveError.denied }
        try await PHPhotoLibrary.shared().performChanges {
            PHAssetChangeRequest.creationRequestForAsset(from: image)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Declare &lt;code&gt;NSPhotoLibraryAddUsageDescription&lt;/code&gt;&lt;/strong&gt; (iOS 11+), not the full-library key. For example: &quot;Save wallpapers you choose to your photo library.&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;requestAuthorization(for: .addOnly)&lt;/code&gt;&lt;/strong&gt; (iOS 14+) asks only for what you need. Users are far more likely to allow it, and you never see their photos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save the original file&lt;/strong&gt; with &lt;code&gt;PHAssetCreationRequest.addResource(with:fileURL:options:)&lt;/code&gt; (iOS 9+). Re-encoding a &lt;code&gt;UIImage&lt;/code&gt; can cost quality and strip metadata.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-the-honest-part-you-can-t-set-the-wallpaper-for-the-user&quot;&gt;7. The honest part: you can't set the wallpaper for the user&lt;/h3&gt;
&lt;p&gt;There's no public iOS API that lets an app set the Lock Screen or Home Screen wallpaper. A wallpaper app's job ends at &quot;saved to Photos&quot;, and the user sets it from the Photos app or from Settings → Wallpaper.&lt;/p&gt;
&lt;p&gt;So make that last step effortless:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;After saving, show a short, illustrated guide: &lt;strong&gt;Photos → open the image → Share → Use as Wallpaper&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Offer a button that opens Photos. Don't promise a one-tap &quot;Set&quot; button you can't deliver; App Review and users both dislike that.&lt;/li&gt;
&lt;li&gt;Mention that the system lets you pinch and position the image, and that depth and motion options depend on the device and iOS version.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The listing's own wording handles this well: &quot;Save the image… Set it as your Lock Screen or Home Screen wallpaper&quot;. It's a user step, described as one.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;8-real-world-example-wallpapers-hd-exotic-4k&quot;&gt;8. Real-world example: Wallpapers HD: Exotic 4K&lt;/h3&gt;
&lt;p&gt;These are the techniques I'd use, and recommend, for &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/us/app/wallpapers-hd-exotic-4k/id6443850234&quot;&gt;Wallpapers HD: Exotic 4K&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bestlatestapps.com/assets/blog/shipping-a-4k-wallpaper-app-without-running-out-of-memory/wallpapers-hd-screenshot-1.jpg&quot; alt=&quot;Wallpapers HD App Store screenshot 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;At the time of writing, its App Store listing shows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Free&lt;/strong&gt; with an optional Premium tier, in &lt;strong&gt;Photo &amp;amp; Video&lt;/strong&gt; and &lt;strong&gt;Graphics &amp;amp; Design&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;First released in &lt;strong&gt;December 2022&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;6 ratings&lt;/strong&gt; in the US store, all &lt;strong&gt;5 stars&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;iOS 16.0 and later&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How the listing maps to this article:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;What the listing says &lt;/th&gt;&lt;th&gt;Where it's covered&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&quot;Fresh 4K HD wallpapers added regularly&quot; / &quot;Browse by category… Quick search&quot; &lt;/td&gt;&lt;td&gt;A small rendition for the grid, downsampled and cost-cached (sections 2–3)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Parallax-ready backgrounds that look sharp on every iPhone display&quot; &lt;/td&gt;&lt;td&gt;Native-size preview, original saved, room for system cropping (section 4)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Create your own wallpapers — edit photos and add blur effects&quot; &lt;/td&gt;&lt;td&gt;Clamped Gaussian blur with optional dimming (section 5)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Save the image… Set it as your Lock Screen or Home Screen wallpaper&quot; &lt;/td&gt;&lt;td&gt;Add-only Photos saving, then a guided user step (sections 6–7)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Free: Browse and download HD wallpapers with ads / Premium: … advanced blur effects… ad-free&quot; &lt;/td&gt;&lt;td&gt;Gate the content and editor features by entitlement; the pipeline is the same&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;https://bestlatestapps.com/assets/blog/shipping-a-4k-wallpaper-app-without-running-out-of-memory/wallpapers-hd-screenshot-2.jpg&quot; alt=&quot;Wallpapers HD App Store screenshot 2&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;9-before-you-ship-an-image-heavy-app-a-checklist&quot;&gt;9. Before you ship an image-heavy app: a checklist&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Nothing is decoded larger than it's displayed.&lt;/strong&gt; Grid cells use downsampled thumbnails sized in pixels.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The cache is costed in decoded bytes&lt;/strong&gt; with a sensible limit, and it responds to memory pressure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scrolling is profiled on your oldest supported device&lt;/strong&gt; with Instruments' Allocations and Animation Hitches tools.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full-resolution images are downloaded to disk&lt;/strong&gt;, previewed at screen size, and saved as original bytes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blur previews run on the small image&lt;/strong&gt; and export at full size with a scaled radius.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Photos access is add-only&lt;/strong&gt;, with &lt;code&gt;NSPhotoLibraryAddUsageDescription&lt;/code&gt; and graceful handling of denial.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You don't promise &quot;set wallpaper&quot; automation.&lt;/strong&gt; Guide the user through Photos instead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;UIScreen.main&lt;/code&gt; is gone from new code.&lt;/strong&gt; Use the environment's &lt;code&gt;displayScale&lt;/code&gt; and the window scene's screen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Image licensing is clear&lt;/strong&gt; for every image you serve.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;A wallpaper app is really a memory-management app with nice pictures. Downsample with ImageIO, cache by decoded cost, keep exactly one full-size image alive, preview edits small and export big, save with the narrowest permission, and be straight with users about the last step only they can take.&lt;/p&gt;
&lt;p&gt;Try it, or explore my other apps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wallpapers HD: Exotic 4K (iOS):&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/us/app/wallpapers-hd-exotic-4k/id6443850234&quot;&gt;https://apps.apple.com/us/app/wallpapers-hd-exotic-4k/id6443850234&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All of my free iOS apps:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/us/developer/alok-singh/id1508795536&quot;&gt;https://apps.apple.com/us/developer/alok-singh/id1508795536&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;My portfolio:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://bestlatestapps.com/alok/&quot;&gt;https://bestlatestapps.com/alok/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What's the largest memory spike an image grid has ever given you? Tell me in the comments.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;— Alok Kumar Singh&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bestlatestapps.com/blog/shipping-a-4k-wallpaper-app-without-running-out-of-memory/&quot;&gt;https://bestlatestapps.com/blog/shipping-a-4k-wallpaper-app-without-running-out-of-memory/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32271/shipping-4k-wallpaper-app-without-running-out-memory-imageio-downsampling-and-core-image</guid>
<pubDate>Sat, 24 Oct 2026 12:40:06 +0000</pubDate>
</item>
<item>
<title>PS4 Pro Belize DDR3 patch v2</title>
<link>https://coderlegion.com/32257/ps4-pro-belize-ddr3-patch-v2</link>
<description>&lt;p&gt;The last time the DDR3 memory on PS4 pro has a critical problem it was capped under 11mb/s speed how much I tied I failed every time &lt;/p&gt;
&lt;p&gt;then I notice something while debugging through the southbridge well it turns out DDR3 has it's own southbridge DMA engine but I don't really notice at first.&lt;/p&gt;
&lt;p&gt;and after some more debugging it turns out my DDR3 chip &lt;em&gt;(1 GB on the southbridge)&lt;/em&gt; was being read by the CPU personally, 8 bytes at a time, through a tiny PCI window ~10.6 MB/s. and that was the main bottleneck of all.&lt;/p&gt;
&lt;p&gt;now it feels like I almost fix the issue but nope the southbridge DMA engine has no Linux driver for PS4 to use it.&lt;/p&gt;
&lt;p&gt;and after more debugging  it turns out Sony's PS4 gameOS aka orbis uses that chip (their sbram driver), but the code is closed. &lt;/p&gt;
&lt;p&gt;So, I used AI to reverse engineer the kernel binary and everything started making sense now&lt;/p&gt;
&lt;p&gt; &lt;em&gt;the chip lives at 0xC0000000 + offset on the internal bus and everything moves through the southbridge DMA engine with exact protocol byte-count, source, dest, then the 0x1d40 &quot;go&quot; register, and write 0x11 to the interrupt-mask register.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;after that it's time to do the main patch The implementation&lt;/p&gt;
&lt;p&gt;On the ps4linux it sees the southbridge DMA engine as a pci device: 104d:90cd. The driver asks linux for it, switches it on, and maps its register memory. Now the engine can't touch program memory directly. everything goes through one special buffer the driver allocates at startup a small chunk of RAM that is always below 4 GB and always coherent (DMA-visible) think of it as the loading dock between the CPU and the DDR3.&lt;/p&gt;
&lt;p&gt;After the patch bake into the kernel and the result &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;old patch &lt;/th&gt;&lt;th&gt;new patch&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;reads   &lt;/td&gt;&lt;td&gt;10.6 MB/s &lt;/td&gt;&lt;td&gt;&lt;strong&gt;504 MB/s&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;writes  &lt;/td&gt;&lt;td&gt;~138 MB/s &lt;/td&gt;&lt;td&gt;&lt;strong&gt;425 MB/s&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ceiling &lt;/td&gt;&lt;td&gt;unknown   &lt;/td&gt;&lt;td&gt;&lt;strong&gt;~1.4 GB/s&lt;/strong&gt; (65,408 B jobs @ 1,383 MB/s)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;The engine itself moves data at up to ~1.4 GB/s. what used to be a 10.6 MB/s unusable brick is now a fast little swap chip on PS4linux&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/5jtjwqvdjlnpidtmpgn6.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32257/ps4-pro-belize-ddr3-patch-v2</guid>
<pubDate>Sat, 24 Oct 2026 08:36:00 +0000</pubDate>
</item>
<item>
<title>Image SEO in 2026: Alt Text, Modern Formats and Fast Loading</title>
<link>https://coderlegion.com/32142/image-seo-in-2026-alt-text-modern-formats-and-fast-loading</link>
<description>&lt;p&gt;Image SEO in 2026 boils down to three jobs: describe every image accurately, deliver it fast, and make it discoverable and attributable. This guide covers each with copy-ready HTML, sticking to what Google, web.dev and the browser makers actually document.&lt;/p&gt;
&lt;p&gt;It starts with a rule that catches many modern sites: Google indexes images in an img element (including inside picture) but not images set as CSS backgrounds. Then description: alt text that says what the image shows and why it's there, in plain words, with empty alt for decoration and any text inside a graphic repeated in the alt. Filenames, captions, nearby text and page titles all help Google understand an image, and the title attribute isn't a substitute for alt.&lt;/p&gt;
&lt;p&gt;For speed, I recommend serving AVIF or WebP through a picture element with a JPEG or PNG fallback (and making sure the server sends the right content type), setting width and height on every image to prevent layout shift, using srcset with sizes so phones don't download desktop files, lazy-loading below the fold, and never lazy-loading the main image, which should get fetchpriority=&quot;high&quot; instead.&lt;/p&gt;
&lt;p&gt;I also cover image sitemaps, the image property in structured data, licence metadata for Google's Licensable badge, and what's actually documented about Google Lens and multimodal AI Mode: use your own photos and explain them in text nearby. A checklist and honest limits close it out.&lt;/p&gt;
&lt;h4 id=&quot;key-takeaways&quot;&gt;Key takeaways&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Use real img elements; CSS background images aren't indexed&lt;/li&gt;
&lt;li&gt;Write alt text that describes what the image shows&lt;/li&gt;
&lt;li&gt;Serve AVIF or WebP with a fallback&lt;/li&gt;
&lt;li&gt;Always set width and height to prevent layout shift&lt;/li&gt;
&lt;li&gt;Lazy-load below the fold, but prioritise the hero image&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Related on BuiltToWinWeb: &lt;a rel=&quot;nofollow&quot; href=&quot;https://built2winweb.com/blog/what-makes-a-website-load-fast/&quot;&gt;what makes a website load fast&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read the full guide on BuiltToWinWeb:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://built2winweb.com/blog/image-seo-2026/&quot;&gt;&lt;strong&gt;The image SEO guide for 2026&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A summary by Jacob Campbell of an article first published on built2winweb.com on October 7, 2026.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32142/image-seo-in-2026-alt-text-modern-formats-and-fast-loading</guid>
<pubDate>Fri, 23 Oct 2026 04:00:46 +0000</pubDate>
</item>
<item>
<title>Why Does a Website Become Slow, and How Can We Fix It?</title>
<link>https://coderlegion.com/32017/why-does-a-website-become-slow-and-how-can-we-fix-it</link>
<description>&lt;p&gt;As a web developer, one thing I find interesting is that building a website is not just about making it look good. A website also needs to be fast, reliable, and easy to use.&lt;/p&gt;
&lt;p&gt;Sometimes, a website looks perfect during development, but after adding more features, images, and data, it starts getting slower. Pages take longer to load, buttons respond late, and users may leave before the website even finishes loading.&lt;/p&gt;
&lt;p&gt;So, what causes this problem, and how can we solve it?&lt;/p&gt;
&lt;h3 id=&quot;the-problem-a-website-that-loads-too-slowly&quot;&gt;The Problem: A Website That Loads Too Slowly&lt;/h3&gt;
&lt;p&gt;Imagine you are building an online store. At first, everything works fine because there are only a few products, images, and pages.&lt;/p&gt;
&lt;p&gt;After some time, the store grows. Hundreds of products are added, images become larger, and the database has to handle more requests.&lt;/p&gt;
&lt;p&gt;Suddenly, the homepage takes several seconds to load.&lt;/p&gt;
&lt;p&gt;Users start complaining, and the website becomes difficult to use, especially on mobile devices or slower internet connections.&lt;/p&gt;
&lt;p&gt;The first reaction might be to rewrite the entire website or upgrade the server. However, that is not always necessary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The better approach is to identify what is actually making the website slow before changing anything.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-1-find-the-real-cause&quot;&gt;Step 1: Find the Real Cause&lt;/h3&gt;
&lt;p&gt;Before fixing performance issues, I would first investigate where the delay comes from.&lt;/p&gt;
&lt;p&gt;There are several possible reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Large images that take too long to download.&lt;/li&gt;
&lt;li&gt;Too much JavaScript running in the browser.&lt;/li&gt;
&lt;li&gt;Database queries that retrieve unnecessary information.&lt;/li&gt;
&lt;li&gt;Too many HTTP requests.&lt;/li&gt;
&lt;li&gt;Missing caching for frequently requested data.&lt;/li&gt;
&lt;li&gt;A server that takes too long to respond.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Browser developer tools and performance testing tools can help identify which part needs attention.&lt;/p&gt;
&lt;p&gt;For example, if the server responds quickly but the page still takes a long time to appear, the problem might be related to images, CSS, or JavaScript rather than the backend.&lt;/p&gt;
&lt;p&gt;This is why guessing is not a good debugging strategy.&lt;/p&gt;
&lt;h3 id=&quot;step-2-optimize-images-and-static-files&quot;&gt;Step 2: Optimize Images and Static Files&lt;/h3&gt;
&lt;p&gt;Images often take up a large part of a webpage's total download size.&lt;/p&gt;
&lt;p&gt;Uploading a large image directly from a camera can make a website unnecessarily heavy.&lt;/p&gt;
&lt;p&gt;A few practical solutions include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compressing images before uploading them.&lt;/li&gt;
&lt;li&gt;Using modern image formats such as WebP or AVIF when appropriate.&lt;/li&gt;
&lt;li&gt;Serving images at dimensions suitable for their display size.&lt;/li&gt;
&lt;li&gt;Loading below-the-fold images only when needed.&lt;/li&gt;
&lt;li&gt;Minifying CSS and JavaScript files where appropriate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These changes can reduce download time without requiring major changes to the application.&lt;/p&gt;
&lt;h3 id=&quot;step-3-improve-database-queries&quot;&gt;Step 3: Improve Database Queries&lt;/h3&gt;
&lt;p&gt;Database performance is another common issue, especially when a website starts handling more users and records.&lt;/p&gt;
&lt;p&gt;Consider a product page that retrieves every column from a database table even though it only needs the product name, price, and image.&lt;/p&gt;
&lt;p&gt;Instead of retrieving unnecessary data, the application can request only the fields it needs.&lt;/p&gt;
&lt;p&gt;For example, a query might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT name, price, image_url
FROM products
WHERE category_id = 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Indexes can also help when queries frequently filter or sort records using particular columns.&lt;/p&gt;
&lt;p&gt;However, indexes should be chosen carefully because they also require storage and can slow down certain write operations.&lt;/p&gt;
&lt;p&gt;For larger datasets, pagination can prevent the application from loading thousands of records at once.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Small database improvements can make a noticeable difference when applied to frequently used pages.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-4-use-caching-where-it-makes-sense&quot;&gt;Step 4: Use Caching Where It Makes Sense&lt;/h3&gt;
&lt;p&gt;Some information does not need to be calculated or retrieved from the database every time someone visits a page.&lt;/p&gt;
&lt;p&gt;For example, a website might display the same list of categories to thousands of users throughout the day.&lt;/p&gt;
&lt;p&gt;Instead of repeatedly running the same query, the application could temporarily store the result in a cache.&lt;/p&gt;
&lt;p&gt;Depending on the application, caching might happen in the browser, at the server, through a reverse proxy, or using a service such as Redis.&lt;/p&gt;
&lt;p&gt;The important thing is to decide which data can safely be cached and how it should be refreshed.&lt;/p&gt;
&lt;p&gt;Caching everything is not a good solution. Information such as account balances or current order status may require more careful handling to avoid displaying outdated data.&lt;/p&gt;
&lt;h3 id=&quot;step-5-reduce-unnecessary-javascript&quot;&gt;Step 5: Reduce Unnecessary JavaScript&lt;/h3&gt;
&lt;p&gt;Modern websites often depend on JavaScript for interactive features, but adding too many libraries can increase loading time.&lt;/p&gt;
&lt;p&gt;A simple page does not always need a large framework or multiple libraries.&lt;/p&gt;
&lt;p&gt;Developers can improve performance by removing unused dependencies, splitting code into smaller bundles, and loading nonessential features only when required.&lt;/p&gt;
&lt;p&gt;It is also worth checking whether an animation, widget, or third-party script is slowing down the initial page load.&lt;/p&gt;
&lt;p&gt;The goal is not to avoid JavaScript. The goal is to use it where it provides real value.&lt;/p&gt;
&lt;h3 id=&quot;step-6-test-the-changes&quot;&gt;Step 6: Test the Changes&lt;/h3&gt;
&lt;p&gt;After making improvements, the next step is to test the website again.&lt;/p&gt;
&lt;p&gt;I would compare the results before and after the changes, paying attention to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Page loading time.&lt;/li&gt;
&lt;li&gt;Server response time.&lt;/li&gt;
&lt;li&gt;Database query duration.&lt;/li&gt;
&lt;li&gt;JavaScript execution time.&lt;/li&gt;
&lt;li&gt;Performance on mobile devices.&lt;/li&gt;
&lt;li&gt;Overall user experience.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tools such as Chrome DevTools and Lighthouse can help identify performance problems and measure improvements.&lt;/p&gt;
&lt;p&gt;Testing matters because a change that improves one page might not improve every part of the application.&lt;/p&gt;
&lt;h3 id=&quot;what-if-the-website-is-still-slow-&quot;&gt;What If the Website Is Still Slow?&lt;/h3&gt;
&lt;p&gt;Sometimes, optimizing images, queries, and scripts is not enough.&lt;/p&gt;
&lt;p&gt;The application may be receiving more traffic than the current infrastructure can comfortably handle. In that situation, it may be necessary to review server resources, application architecture, background jobs, or database capacity.&lt;/p&gt;
&lt;p&gt;A content delivery network can help deliver static files to users from locations closer to them. Background processing can also move time-consuming tasks, such as sending emails or generating reports, away from the main request.&lt;/p&gt;
&lt;p&gt;But these solutions should follow evidence, not assumptions.&lt;/p&gt;
&lt;p&gt;Upgrading infrastructure before understanding the problem can increase costs without fixing the real cause.&lt;/p&gt;
&lt;h3 id=&quot;my-main-takeaway&quot;&gt;My Main Takeaway&lt;/h3&gt;
&lt;p&gt;Web development does not end when a page looks correct or a feature starts working. A good website should continue to perform well as its content, features, and number of users grow.&lt;/p&gt;
&lt;p&gt;When a website becomes slow, I would start by measuring its performance, finding the biggest bottleneck, and fixing one issue at a time.&lt;/p&gt;
&lt;p&gt;Sometimes, compressing a few images is enough. In other cases, improving a database query or introducing caching can make a much bigger difference.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The most useful lesson is to understand the problem before choosing the solution.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every performance issue is an opportunity to learn something new about how websites work, from the browser to the server and the database.&lt;/p&gt;
&lt;p&gt;I think this is one of the things that makes web development interesting: there is always another way to make an application more reliable, efficient, and easier to use.&lt;/p&gt;
&lt;p&gt;What do you usually check first when a website starts getting slow: the frontend, the backend, or the database?&lt;/p&gt;
&lt;p&gt;I'd be interested to hear how other developers approach this problem.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32017/why-does-a-website-become-slow-and-how-can-we-fix-it</guid>
<pubDate>Thu, 22 Oct 2026 03:20:22 +0000</pubDate>
</item>
<item>
<title>INP, the Core Web Vital That Replaced FID: How to Fix It</title>
<link>https://coderlegion.com/31420/inp-the-core-web-vital-that-replaced-fid-how-to-fix-it</link>
<description>&lt;p&gt;Interaction to Next Paint tracks how fast a page shows a visible reaction to a click, tap or key press. It took over from First Input Delay as a Core Web Vital on March 12, 2024. Good is 200 milliseconds or less and poor is over 500, measured at the 75th percentile of real visits. Many sites that passed FID easily fail INP because it covers every interaction in a visit and includes running your code and painting the result.&lt;/p&gt;
&lt;p&gt;In the guide I explain what counts as an interaction (clicks, taps and key presses, not scrolling or hovering) and the three phases of each one: input delay, processing duration and presentation delay. Knowing which phase is slow tells you which fix applies.&lt;/p&gt;
&lt;p&gt;Measurement starts with field data from CrUX, PageSpeed Insights and Search Console, then moves to DevTools with CPU throttling to reproduce problems. The web-vitals library's attribution build tells you which element was slow for real visitors and how long each phase took.&lt;/p&gt;
&lt;p&gt;The fixes come in three groups: break up long tasks and yield to the main thread (with scheduler.yield() where supported); cut rendering work by avoiding layout thrashing, keeping the DOM small and using content-visibility; and tame input handlers with debouncing while deferring or removing third-party scripts. I'm clear about limits too: INP isn't a ranking shortcut, field data moves slowly, and a fast spinner doesn't fix slow work.&lt;/p&gt;
&lt;h4 id=&quot;key-takeaways-1&quot;&gt;Key takeaways&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Good INP is 200 ms or less at the 75th percentile&lt;/li&gt;
&lt;li&gt;Each interaction has input, processing and presentation phases&lt;/li&gt;
&lt;li&gt;Use field data first; Lighthouse doesn't measure INP&lt;/li&gt;
&lt;li&gt;Break up long tasks and yield to the main thread&lt;/li&gt;
&lt;li&gt;Debounce heavy handlers and audit third-party scripts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Related on BuiltToWinWeb: &lt;a rel=&quot;nofollow&quot; href=&quot;https://built2winweb.com/core-web-vitals-explained/&quot;&gt;Core Web Vitals explained&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read the full guide on BuiltToWinWeb: &lt;a rel=&quot;nofollow&quot; href=&quot;https://built2winweb.com/blog/inp-core-web-vitals/&quot;&gt;The full INP explainer and fixes&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A summary by Jacob Campbell of an article first published on built2winweb.com on October 6, 2026.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31420/inp-the-core-web-vital-that-replaced-fid-how-to-fix-it</guid>
<pubDate>Sun, 18 Oct 2026 07:09:33 +0000</pubDate>
</item>
<item>
<title>🔥 30 Days of Frontend — Day 5: React DevTools Profiler</title>
<link>https://coderlegion.com/31532/30-days-of-frontend-day-5-react-devtools-profiler</link>
<description>&lt;p&gt;When a React application starts feeling slow, one of the first things developers often do is reach for &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, or &lt;code&gt;React.memo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We've all seen this pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A component renders more often than expected.&lt;/li&gt;
&lt;li&gt;The UI feels sluggish when interacting with a page.&lt;/li&gt;
&lt;li&gt;A large list becomes noticeably slower as more items are added.&lt;/li&gt;
&lt;li&gt;Updating one piece of state seems to trigger work across an entire component tree.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The natural reaction is to start memoizing components and functions.&lt;/p&gt;
&lt;p&gt;But here's the problem: &lt;strong&gt;you might be optimizing code that isn't responsible for the slowdown in the first place.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;React performance optimization should start with understanding what is happening, not guessing what might be happening.&lt;/p&gt;
&lt;p&gt;That's where the &lt;strong&gt;React DevTools Profiler&lt;/strong&gt; becomes useful.&lt;/p&gt;
&lt;h3 id=&quot;1-what-is-the-react-devtools-profiler-&quot;&gt;1. What Is the React DevTools Profiler?&lt;/h3&gt;
&lt;p&gt;React DevTools is a browser extension that helps developers inspect React component trees, props, state, and other component-related information.&lt;/p&gt;
&lt;p&gt;It includes two particularly useful panels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Components:&lt;/strong&gt; Inspect the component hierarchy, props, state, and component details.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Profiler:&lt;/strong&gt; Record rendering activity and investigate how React updated the component tree.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Profiler helps you understand the rendering work associated with a particular interaction.&lt;/p&gt;
&lt;p&gt;For example, imagine you're working on an e-commerce application with a product grid containing hundreds of products.&lt;/p&gt;
&lt;p&gt;When a user changes a filter, several things might happen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The parent component updates.&lt;/li&gt;
&lt;li&gt;The product list renders again.&lt;/li&gt;
&lt;li&gt;Individual product cards render.&lt;/li&gt;
&lt;li&gt;Sorting or filtering calculations run.&lt;/li&gt;
&lt;li&gt;Some components render even though their visible data hasn't changed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of this work might be necessary. Some might be avoidable.&lt;/p&gt;
&lt;p&gt;Instead of assuming which component is responsible, you can record the interaction and inspect the results.&lt;/p&gt;
&lt;p&gt;The goal isn't to eliminate every render. It's to understand whether the work is necessary and whether it affects the user experience.&lt;/p&gt;
&lt;h3 id=&quot;2-understanding-how-react-rendering-relates-to-profiling&quot;&gt;2. Understanding How React Rendering Relates to Profiling&lt;/h3&gt;
&lt;p&gt;Before opening the Profiler, it's worth understanding what it actually measures.&lt;/p&gt;
&lt;p&gt;A React update generally involves two important phases.&lt;/p&gt;
&lt;h4 id=&quot;the-render-phase&quot;&gt;The render phase&lt;/h4&gt;
&lt;p&gt;During the render phase, React calls components as needed to determine what the next UI should look like.&lt;/p&gt;
&lt;p&gt;This phase includes calculating the next React element tree and determining the changes required.&lt;/p&gt;
&lt;p&gt;A component rendering doesn't automatically mean that the browser DOM will change.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function Counter({ count }) {
  console.log(&quot;Counter rendered&quot;);

  return &amp;lt;p&amp;gt;Count: {count}&amp;lt;/p&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever React calls &lt;code&gt;Counter&lt;/code&gt;, the component function executes and returns a React element describing the UI.&lt;/p&gt;
&lt;p&gt;React may then determine that the resulting output doesn't require a DOM mutation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A render is not the same thing as a DOM update.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This distinction is important because developers sometimes interpret every component render as expensive browser work.&lt;/p&gt;
&lt;h4 id=&quot;the-commit-phase&quot;&gt;The commit phase&lt;/h4&gt;
&lt;p&gt;After React finishes the necessary render work, it can commit the changes to the host environment, such as updating the DOM in a browser.&lt;/p&gt;
&lt;p&gt;The Profiler helps you investigate rendering activity, but its measurements shouldn't be interpreted as a complete measurement of every part of browser performance.&lt;/p&gt;
&lt;p&gt;For example, a page can have relatively inexpensive React rendering but still feel slow because of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Expensive JavaScript outside React.&lt;/li&gt;
&lt;li&gt;Large amounts of DOM layout or painting.&lt;/li&gt;
&lt;li&gt;Long-running event handlers.&lt;/li&gt;
&lt;li&gt;Network delays.&lt;/li&gt;
&lt;li&gt;Large images or other resources.&lt;/li&gt;
&lt;li&gt;Main-thread work that blocks user interactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is why the React Profiler is one tool in a broader performance investigation, not a replacement for the browser's Performance panel.&lt;/p&gt;
&lt;h3 id=&quot;3-what-can-you-learn-from-the-profiler-&quot;&gt;3. What Can You Learn From the Profiler?&lt;/h3&gt;
&lt;p&gt;When you record a profiling session, the Profiler provides several useful ways to understand React's work.&lt;/p&gt;
&lt;h4 id=&quot;a-which-components-rendered-&quot;&gt;A. Which components rendered?&lt;/h4&gt;
&lt;p&gt;The Profiler helps you identify the components involved in an update.&lt;/p&gt;
&lt;p&gt;Suppose your component tree looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;App
├── Header
├── SearchBar
├── FilterSidebar
└── ProductGrid
    ├── ProductCard
    ├── ProductCard
    ├── ProductCard
    └── ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the user types into the search field, you might discover that &lt;code&gt;SearchBar&lt;/code&gt;, &lt;code&gt;ProductGrid&lt;/code&gt;, and many &lt;code&gt;ProductCard&lt;/code&gt; instances rendered.&lt;/p&gt;
&lt;p&gt;That doesn't automatically mean all those renders are unnecessary.&lt;/p&gt;
&lt;p&gt;You need to understand why they rendered and whether the work was expensive.&lt;/p&gt;
&lt;p&gt;The component tree gives you a starting point for that investigation.&lt;/p&gt;
&lt;h4 id=&quot;b-how-often-did-components-render-&quot;&gt;B. How often did components render?&lt;/h4&gt;
&lt;p&gt;Repeated rendering can be perfectly normal.&lt;/p&gt;
&lt;p&gt;For example, typing into a controlled input typically updates state as the user types:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function SearchBar() {
  const [query, setQuery] = React.useState(&quot;&quot;);

  return (
    &amp;lt;input
      value={query}
      onChange={(event) =&amp;gt; {
        setQuery(event.target.value);
      }}
      placeholder=&quot;Search products...&quot;
    /&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each keystroke can trigger an update.&lt;/p&gt;
&lt;p&gt;That's expected behavior.&lt;/p&gt;
&lt;p&gt;The more interesting question is what else happens during those updates.&lt;/p&gt;
&lt;p&gt;If typing into the search field also triggers expensive calculations or unnecessary rendering across a large product grid, the Profiler can help you investigate that work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Render frequency is a clue, not a performance verdict.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;c-what-caused-a-component-to-render-&quot;&gt;C. What caused a component to render?&lt;/h4&gt;
&lt;p&gt;This is one of the most useful parts of profiling.&lt;/p&gt;
&lt;p&gt;A component might render because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Its state changed.&lt;/li&gt;
&lt;li&gt;Its parent rendered.&lt;/li&gt;
&lt;li&gt;Its props changed.&lt;/li&gt;
&lt;li&gt;Its context value changed.&lt;/li&gt;
&lt;li&gt;An external store or another subscription triggered an update.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The exact cause depends on the component and the update mechanism.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function ProductPage() {
  const [query, setQuery] = React.useState(&quot;&quot;);
  const [selectedCategory, setSelectedCategory] =
    React.useState(&quot;all&quot;);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;SearchInput
        value={query}
        onChange={setQuery}
      /&amp;gt;

      &amp;lt;CategoryFilter
        value={selectedCategory}
        onChange={setSelectedCategory}
      /&amp;gt;

      &amp;lt;ProductGrid query={query} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;selectedCategory&lt;/code&gt; changes, &lt;code&gt;ProductPage&lt;/code&gt; renders again.&lt;/p&gt;
&lt;p&gt;Because &lt;code&gt;ProductGrid&lt;/code&gt; is a child of &lt;code&gt;ProductPage&lt;/code&gt;, React may also render &lt;code&gt;ProductGrid&lt;/code&gt;, even though its &lt;code&gt;query&lt;/code&gt; prop hasn't changed.&lt;/p&gt;
&lt;p&gt;That can be a candidate for optimization, but it isn't automatically a problem. First, determine whether the work is expensive enough to matter.&lt;/p&gt;
&lt;p&gt;Also, remember that React DevTools' ability to display detailed render reasons depends on the React version, development tooling, and available settings. Don't assume every render will always have a definitive explanation displayed.&lt;/p&gt;
&lt;h3 id=&quot;4-how-to-use-react-devtools-profiler-a-step-by-step-workflow&quot;&gt;4. How to Use React DevTools Profiler: A Step-by-Step Workflow&lt;/h3&gt;
&lt;p&gt;Let's turn this into a practical workflow.&lt;/p&gt;
&lt;h4 id=&quot;step-1-reproduce-the-actual-performance-problem&quot;&gt;Step 1: Reproduce the actual performance problem&lt;/h4&gt;
&lt;p&gt;Start with the user interaction that feels slow.&lt;/p&gt;
&lt;p&gt;Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Typing into a search field.&lt;/li&gt;
&lt;li&gt;Changing a product filter.&lt;/li&gt;
&lt;li&gt;Switching tabs.&lt;/li&gt;
&lt;li&gt;Expanding a large navigation menu.&lt;/li&gt;
&lt;li&gt;Updating a dashboard with multiple charts.&lt;/li&gt;
&lt;li&gt;Scrolling through a long list.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avoid profiling random interactions without a clear question.&lt;/p&gt;
&lt;p&gt;A useful starting question might be:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Why does changing a product filter cause so much rendering work?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This gives you a specific behavior to investigate.&lt;/p&gt;
&lt;h4 id=&quot;step-2-open-react-devtools&quot;&gt;Step 2: Open React DevTools&lt;/h4&gt;
&lt;p&gt;Install React Developer Tools for your browser if you haven't already.&lt;/p&gt;
&lt;p&gt;Open your application, launch the browser's developer tools, and locate the &lt;strong&gt;Profiler&lt;/strong&gt; tab.&lt;/p&gt;
&lt;p&gt;Make sure you're profiling the application and interaction you want to investigate.&lt;/p&gt;
&lt;p&gt;For realistic performance measurements, prefer a production build or a profiling-enabled production build where appropriate. Development mode can introduce extra overhead, and Strict Mode may intentionally invoke rendering-related logic more than once to help reveal bugs.&lt;/p&gt;
&lt;p&gt;Development profiling is still valuable for understanding component behavior, but don't assume its timings represent production performance.&lt;/p&gt;
&lt;h4 id=&quot;step-3-start-recording&quot;&gt;Step 3: Start recording&lt;/h4&gt;
&lt;p&gt;In the Profiler tab, start a recording.&lt;/p&gt;
&lt;p&gt;Then perform the exact interaction that causes the slowdown.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start recording.&lt;/li&gt;
&lt;li&gt;Type a search query.&lt;/li&gt;
&lt;li&gt;Change a category filter.&lt;/li&gt;
&lt;li&gt;Wait for the UI to settle.&lt;/li&gt;
&lt;li&gt;Stop recording.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Keep the recording focused.&lt;/p&gt;
&lt;h4 id=&quot;step-4-inspect-the-recorded-commits&quot;&gt;Step 4: Inspect the recorded commits&lt;/h4&gt;
&lt;p&gt;The Profiler organizes recorded updates into commits.&lt;/p&gt;
&lt;p&gt;A commit represents a point at which React applied an update to the host environment&lt;/p&gt;
&lt;p&gt;Select a commit related to your interaction, then inspect the available visualization and component details.&lt;/p&gt;
&lt;p&gt;Look for patterns such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A large subtree rendering during a small interaction.&lt;/li&gt;
&lt;li&gt;An expensive component repeatedly appearing in relevant commits.&lt;/li&gt;
&lt;li&gt;Components rendering even though their inputs appear unchanged.&lt;/li&gt;
&lt;li&gt;Expensive calculations associated with frequently occurring updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don't immediately optimize the component with the largest-looking bar. First establish what the measurement represents and whether that component is related to the user-visible problem.&lt;/p&gt;
&lt;h4 id=&quot;step-5-make-one-targeted-change&quot;&gt;Step 5: Make one targeted change&lt;/h4&gt;
&lt;p&gt;Change one thing at a time whenever possible.&lt;/p&gt;
&lt;p&gt;If you simultaneously add &lt;code&gt;React.memo&lt;/code&gt;, introduce several &lt;code&gt;useMemo&lt;/code&gt; calls, restructure the component tree, and change how state is managed, you won't know which change made the difference.&lt;/p&gt;
&lt;p&gt;A focused experiment makes the results easier to interpret.&lt;/p&gt;
&lt;h4 id=&quot;step-6-profile-again&quot;&gt;Step 6: Profile again&lt;/h4&gt;
&lt;p&gt;Repeat the same interaction under comparable conditions.&lt;/p&gt;
&lt;p&gt;Compare:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Actual rendering duration.&lt;/li&gt;
&lt;li&gt;Which components rendered.&lt;/li&gt;
&lt;li&gt;How much of the subtree participated in the update.&lt;/li&gt;
&lt;li&gt;Whether the interaction feels more responsive.&lt;/li&gt;
&lt;li&gt;Whether the optimization introduced additional complexity or bugs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the measured React work decreased but the interaction still feels slow, investigate other parts of the performance pipeline.&lt;/p&gt;
&lt;p&gt;An optimization isn't complete until you've verified its effect.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The React DevTools Profiler helps turn vague performance complaints into concrete questions about rendering behavior.&lt;/p&gt;
&lt;p&gt;Instead of asking:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Why is React so slow?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Start by asking:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Where is my application actually spending time, and what evidence do I have?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That shift—from guessing to measuring—is what makes performance optimization more systematic.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/31532/30-days-of-frontend-day-5-react-devtools-profiler</guid>
<pubDate>Sat, 17 Oct 2026 16:39:44 +0000</pubDate>
</item>
<item>
<title>The Memory Leak in addEventListener You’re Ignoring</title>
<link>https://coderlegion.com/30737/the-memory-leak-in-addeventlistener-youre-ignoring</link>
<description>&lt;p&gt;Event listeners on dynamic elements or SPA routing are silent performance killers. If you attach an event listener to window or a persistent parent node inside a component and don’t clean it up when the component unmounts, that DOM reference stays in memory forever.&lt;/p&gt;
&lt;p&gt;JavaScript&lt;br&gt;
// ❌ Dangerous in Single Page Applications&lt;br&gt;
function setupListener() {&lt;br&gt;
  window.addEventListener('resize', handleResize);&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;// ✅ Use AbortController for clean, single-call teardown&lt;br&gt;
const controller = new AbortController();&lt;br&gt;
const { signal } = controller;&lt;/p&gt;
&lt;p&gt;window.addEventListener('resize', handleResize, { signal });&lt;/p&gt;
&lt;p&gt;// Call this anywhere during unmount/cleanup:&lt;br&gt;
controller.abort(); // Automatically removes ALL listeners bound to this signal!&lt;br&gt;
Why it changes your career: Knowing how browser memory management works at runtime elevates you from a basic script author to a senior engineer who writes production-ready apps that don't crash under load.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30737/the-memory-leak-in-addeventlistener-youre-ignoring</guid>
<pubDate>Thu, 15 Oct 2026 06:48:00 +0000</pubDate>
</item>
<item>
<title>Why Does a Website Get Slower Over Time? A Web Developer’s Approach to Finding the Real Problem</title>
<link>https://coderlegion.com/31063/why-does-website-get-slower-over-time-web-developers-approach-to-finding-the-real-problem</link>
<description>&lt;p&gt;As a web developer, one thing I’ve learned is that a website can work perfectly during development but behave very differently after real users start using it.&lt;/p&gt;
&lt;p&gt;A page loads quickly when there are only a few records in the database. Buttons respond immediately, and everything feels smooth. But after a few months, the same page starts taking longer to load. Users complain about delays, search results appear slowly, and sometimes the browser freezes for a few seconds.&lt;/p&gt;
&lt;p&gt;The first reaction might be to upgrade the server or rewrite part of the application. But the real problem could be much simpler.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A slow website is not always a server problem. Sometimes, the issue is hidden in the way we write queries, load data, or handle things in the browser.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s look at a common web development problem and how to approach it step by step.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-everything-works-fine-until-the-data-grows&quot;&gt;The Problem: Everything Works Fine Until the Data Grows&lt;/h3&gt;
&lt;p&gt;Imagine building an online store with a product listing page.&lt;/p&gt;
&lt;p&gt;At the beginning, the database contains only 100 products. The page loads quickly, and users can search, filter, and browse products without any trouble.&lt;/p&gt;
&lt;p&gt;A few months later, the store has 50,000 products. Suddenly, the product page takes several seconds to load.&lt;/p&gt;
&lt;p&gt;The application still works, but the user experience is getting worse.&lt;/p&gt;
&lt;p&gt;There are several possible reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The application loads too many records at once.&lt;/li&gt;
&lt;li&gt;Database queries are taking longer than expected.&lt;/li&gt;
&lt;li&gt;The same data is requested multiple times.&lt;/li&gt;
&lt;li&gt;Images and JavaScript files are unnecessarily large.&lt;/li&gt;
&lt;li&gt;The frontend performs too much work before displaying the page.&lt;/li&gt;
&lt;li&gt;The server makes repeated requests to other services.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The important thing is not to assume which problem is responsible. &lt;strong&gt;The first step should be finding out where the time is actually being spent.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-1-find-the-bottleneck-before-changing-the-code&quot;&gt;Step 1: Find the Bottleneck Before Changing the Code&lt;/h3&gt;
&lt;p&gt;When a page becomes slow, I would start by checking its behavior instead of immediately changing the implementation.&lt;/p&gt;
&lt;p&gt;The browser's developer tools can help identify slow network requests, large resources, and JavaScript execution issues. Server logs and application monitoring can reveal slow endpoints, while database query logs can show expensive queries.&lt;/p&gt;
&lt;p&gt;For example, suppose the product page takes four seconds to load.&lt;/p&gt;
&lt;p&gt;The investigation might reveal that the initial HTML response takes only 200 milliseconds, but the API request takes 2.5 seconds. The remaining time is spent loading images and rendering the page.&lt;/p&gt;
&lt;p&gt;That changes the direction of the investigation. There is little reason to optimize the initial HTML if the API is responsible for most of the delay.&lt;/p&gt;
&lt;p&gt;A useful habit is to measure the important parts separately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; How long does the browser take to render the page?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backend:&lt;/strong&gt; How long does the API take to return a response?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Database:&lt;/strong&gt; Which queries consume the most time?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network:&lt;/strong&gt; Are requests or assets unnecessarily large?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without measurements, optimization quickly becomes guesswork.&lt;/p&gt;
&lt;h3 id=&quot;step-2-stop-loading-everything-at-once&quot;&gt;Step 2: Stop Loading Everything at Once&lt;/h3&gt;
&lt;p&gt;One common mistake is returning every matching record from the database and allowing the frontend to deal with the result.&lt;/p&gt;
&lt;p&gt;Consider this simplified JavaScript example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;async function getProducts() {
  const response = await fetch(&quot;/api/products&quot;);
  const products = await response.json();

  return products;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code is not inherently wrong. The problem appears when &lt;code&gt;/api/products&lt;/code&gt; returns tens of thousands of records, including fields the page does not need.&lt;/p&gt;
&lt;p&gt;The browser must download the response, parse the JSON, and process the data. The server and database also have to handle a much larger request than necessary.&lt;/p&gt;
&lt;p&gt;A better approach is to introduce pagination.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;async function getProducts(page = 1) {
  const response = await fetch(
    `/api/products?page=${page}&amp;amp;pageSize=20`
  );

  if (!response.ok) {
    throw new Error(&quot;Failed to load products&quot;);
  }

  return response.json();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the frontend requests a smaller set of products at a time.&lt;/p&gt;
&lt;p&gt;The backend must also implement pagination correctly; adding query parameters alone does not reduce the amount of data returned.&lt;/p&gt;
&lt;p&gt;For large datasets, the database query should limit the number of records it retrieves. The API should return only the fields the page needs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal is not to load all available data. It is to load the data needed for the current task.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-3-look-for-inefficient-database-queries&quot;&gt;Step 3: Look for Inefficient Database Queries&lt;/h3&gt;
&lt;p&gt;Sometimes, pagination helps but the page is still slow.&lt;/p&gt;
&lt;p&gt;The next place to investigate is the database.&lt;/p&gt;
&lt;p&gt;Imagine displaying a list of 100 products alongside their category names. A poorly designed implementation might execute one query to retrieve the products and then another query for every product to retrieve its category.&lt;/p&gt;
&lt;p&gt;That could mean 101 database queries for a single page.&lt;/p&gt;
&lt;p&gt;This pattern is often called the N+1 query problem.&lt;/p&gt;
&lt;p&gt;A better solution is to retrieve related information efficiently, using an appropriate join, eager loading, or another strategy supported by the database framework.&lt;/p&gt;
&lt;p&gt;For example, in Ruby on Rails:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;@products = Product
  .includes(:category)
  .order(created_at: :desc)
  .limit(20)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells Rails to load the associated categories efficiently instead of triggering an individual association query every time the view accesses a category.&lt;/p&gt;
&lt;p&gt;The correct approach depends on the associations and how the data is used, so the generated SQL should still be inspected.&lt;/p&gt;
&lt;p&gt;Database indexes are another important consideration. If users frequently search products by SKU or filter by a particular field, an appropriate index may improve query performance significantly.&lt;/p&gt;
&lt;p&gt;However, adding indexes everywhere is not a good solution. Indexes consume storage and can slow down writes. They should be selected based on actual query patterns and execution plans.&lt;/p&gt;
&lt;h3 id=&quot;step-4-make-the-frontend-do-less-work&quot;&gt;Step 4: Make the Frontend Do Less Work&lt;/h3&gt;
&lt;p&gt;A website can have a fast API and still feel slow.&lt;/p&gt;
&lt;p&gt;For example, a frontend might render hundreds of unnecessary elements, download oversized images, or execute expensive calculations whenever a component updates.&lt;/p&gt;
&lt;p&gt;Some practical improvements include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Loading images only when they are needed.&lt;/li&gt;
&lt;li&gt;Serving appropriately sized, compressed images.&lt;/li&gt;
&lt;li&gt;Removing unused JavaScript and unnecessary dependencies.&lt;/li&gt;
&lt;li&gt;Avoiding repeated calculations during rendering.&lt;/li&gt;
&lt;li&gt;Reducing unnecessary API requests.&lt;/li&gt;
&lt;li&gt;Displaying useful loading states while data is being fetched.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is also important to remember that users may have slower devices or unreliable internet connections. A page that feels fast on a powerful development computer might perform poorly on a budget phone.&lt;/p&gt;
&lt;p&gt;Performance should be tested under realistic conditions rather than judged only by how quickly a developer's own machine loads the page.&lt;/p&gt;
&lt;h3 id=&quot;step-5-use-caching-where-it-actually-helps&quot;&gt;Step 5: Use Caching Where It Actually Helps&lt;/h3&gt;
&lt;p&gt;Suppose the store's homepage displays the same featured products to thousands of visitors.&lt;/p&gt;
&lt;p&gt;If the application repeatedly runs the same expensive query to retrieve information that rarely changes, caching might reduce the amount of repeated work.&lt;/p&gt;
&lt;p&gt;Depending on the application, useful options include HTTP caching, application-level caching, and database query optimization.&lt;/p&gt;
&lt;p&gt;For example, Rails provides a caching interface that can be used to store frequently requested results:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;featured_products = Rails.cache.fetch(
  &quot;featured_products&quot;,
  expires_in: 10.minutes
) do
  Product.where(featured: true)
         .order(created_at: :desc)
         .limit(10)
         .to_a
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next request can reuse the cached result until it expires or is invalidated.&lt;/p&gt;
&lt;p&gt;But caching introduces another problem: keeping the data up to date.&lt;/p&gt;
&lt;p&gt;If a featured product changes and the cache still contains an old result, users may see outdated information. Sensitive or user-specific data also needs careful cache handling to avoid exposing one user's information to another.&lt;/p&gt;
&lt;p&gt;Caching should be used when repeated work is expensive and the freshness requirements are understood. It is not a replacement for fixing an inefficient query.&lt;/p&gt;
&lt;h3 id=&quot;step-6-measure-the-improvement&quot;&gt;Step 6: Measure the Improvement&lt;/h3&gt;
&lt;p&gt;After making changes, the job is not finished.&lt;/p&gt;
&lt;p&gt;The application should be tested again under comparable conditions. Otherwise, it is difficult to know whether the changes actually helped.&lt;/p&gt;
&lt;p&gt;Suppose the product API originally took 2.5 seconds to respond. After implementing pagination and fixing inefficient database queries, it takes 400 milliseconds under the same test conditions.&lt;/p&gt;
&lt;p&gt;That would be a meaningful improvement.&lt;/p&gt;
&lt;p&gt;I would also check whether the changes introduced any new problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Are all required products still displayed?&lt;/li&gt;
&lt;li&gt;Do filters and search work correctly?&lt;/li&gt;
&lt;li&gt;Does pagination behave properly when records are added or removed?&lt;/li&gt;
&lt;li&gt;Are database queries still efficient with realistic data volumes?&lt;/li&gt;
&lt;li&gt;Does the application remain responsive when multiple users access it?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Performance improvements should not come at the cost of correctness.&lt;/p&gt;
&lt;h3 id=&quot;what-about-websites-built-with-ruby-on-rails-&quot;&gt;What About Websites Built With Ruby on Rails?&lt;/h3&gt;
&lt;p&gt;Ruby on Rails provides useful tools for addressing these problems, but it does not automatically prevent every performance issue.&lt;/p&gt;
&lt;p&gt;For a Rails application, I would pay particular attention to Active Record queries, association loading, database indexes, caching, background jobs, and response sizes.&lt;/p&gt;
&lt;p&gt;For example, sending emails, generating reports, or processing large files may not need to happen during the user's web request. Moving suitable work to background jobs can make the application more responsive while allowing the task to finish separately.&lt;/p&gt;
&lt;p&gt;For frontend-heavy applications that use JavaScript or Angular, I would also inspect bundle sizes, rendering behavior, and API communication.&lt;/p&gt;
&lt;p&gt;The exact solution depends on the application. There is no single optimization that makes every website faster.&lt;/p&gt;
&lt;h3 id=&quot;the-bigger-lesson-do-not-optimize-blindly&quot;&gt;The Bigger Lesson: Do Not Optimize Blindly&lt;/h3&gt;
&lt;p&gt;One of the easiest mistakes in software development is changing code because it looks inefficient without checking whether it causes a real problem.&lt;/p&gt;
&lt;p&gt;A complicated rewrite might take days and produce little improvement. A small database index or a simple pagination change might solve the actual bottleneck.&lt;/p&gt;
&lt;p&gt;That is why a disciplined approach matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Measure first, identify the bottleneck, make one meaningful change, and measure again.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This approach makes debugging easier because each change has a clear purpose. It also reduces the risk of introducing unnecessary complexity into an application that already works.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;A website becoming slower as it grows is a normal challenge in web development, but it should not be ignored. Slow pages frustrate users, waste server resources, and can make an otherwise useful application feel unreliable.&lt;/p&gt;
&lt;p&gt;The solution usually begins with understanding what has changed. More data, inefficient queries, oversized responses, unnecessary frontend work, and repeated operations can all contribute to the problem.&lt;/p&gt;
&lt;p&gt;As developers, we do not need to make every part of an application complicated in the name of performance. We need to understand the problem well enough to choose the simplest solution that works.&lt;/p&gt;
&lt;p&gt;Every slow page is an opportunity to learn more about how the browser, application server, and database work together.&lt;/p&gt;
&lt;p&gt;And sometimes, the best performance improvement is not writing more code. It is finding the code that does not need to run in the first place.&lt;/p&gt;
&lt;p&gt;I’d be interested to hear how other developers approach this in their projects.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31063/why-does-website-get-slower-over-time-web-developers-approach-to-finding-the-real-problem</guid>
<pubDate>Wed, 14 Oct 2026 12:11:34 +0000</pubDate>
</item>
<item>
<title>Python 3.15 Is Not a Speed Release for Data Teams. It Is an Operations Release.</title>
<link>https://coderlegion.com/30776/python-3-15-is-not-a-speed-release-for-data-teams-it-is-an-operations-release</link>
<description>&lt;p&gt;Originally published on DEV.to: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/firfircelik/python-315-is-not-a-speed-release-for-data-teams-it-is-an-operations-release-5fbo&quot;&gt;https://dev.to/firfircelik/python-315-is-not-a-speed-release-for-data-teams-it-is-an-operations-release-5fbo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Python 3.15.0 shipped on October 9, 2026 (&lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.python.org/2026/10/python-3150-final-is-here/&quot;&gt;release post&lt;/a&gt;). The headline number most people will repeat is in the JIT section of &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.python.org/3.15/whatsnew/3.15.html&quot;&gt;What's New&lt;/a&gt;: a &quot;7-8% geometric mean performance improvement&quot; over the standard interpreter on x86-64 Linux, and 11-12% on AArch64 macOS.&lt;/p&gt;
&lt;p&gt;For a data team, the tempting reading is &quot;upgrade the job images, pipelines get faster.&quot; I installed 3.15.0 on a VM, ran a small pandas/pyarrow workload with the JIT off and on, and then spent most of my time on three features that do not touch throughput at all. Those three are the ones that change how you run Python next week.&lt;/p&gt;
&lt;h3 id=&quot;the-thesis&quot;&gt;The thesis&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;For data teams, Python 3.15 is an operations release, not a speed release.&lt;/strong&gt; The features that matter are about startup (PEP 810 lazy imports), observability (the Tachyon sampling profiler and frame pointers on by default) and supply chain (PEP 829 &lt;code&gt;.start&lt;/code&gt; files replacing &lt;code&gt;import&lt;/code&gt; lines in &lt;code&gt;.pth&lt;/code&gt; files).&lt;/p&gt;
&lt;p&gt;Each of them also has a trap that the release notes do not spell out. I hit all of them on the VM, and they are documented below with the commands to reproduce them.&lt;/p&gt;
&lt;h3 id=&quot;the-antithesis-at-full-strength&quot;&gt;The antithesis, at full strength&lt;/h3&gt;
&lt;p&gt;The best counter-case is not silly. It goes like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The JIT is real now.&lt;/strong&gt; What's New lists a new tracing frontend, basic register allocation, more constant propagation and reference count elimination. The pyperformance range is wide, &quot;from roughly 15% slowdown to over 100% speedup&quot;, but the geometric mean is positive on both platforms.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data engineers write more Python-level loops than they admit.&lt;/strong&gt; Row-wise &lt;code&gt;apply&lt;/code&gt;, per-record parsing, UDFs, validation code. That is exactly the code a JIT can see.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Free-threading got a stable ABI.&lt;/strong&gt; PEP 803 adds &lt;code&gt;abi3t&lt;/code&gt;: extensions that target the Stable ABI can now be compiled so they are compatible with free-threaded builds. That is the long road to real multi-core Python for CPU-bound pipeline code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the antithesis is: the startup and tooling features are nice, but the throughput work is what will show up on the cloud bill.&lt;/p&gt;
&lt;p&gt;Here is what that looked like on the VM.&lt;/p&gt;
&lt;h4 id=&quot;setup&quot;&gt;Setup&lt;/h4&gt;
&lt;p&gt;Everything below ran on one shared VM: 8 vCPU Intel Xeon, 15 GiB RAM, Debian 13, kernel 6.12. Python 3.15.0 is the uv-managed build (Clang 22, tail-calling interpreter, JIT compiled in but off by default, turned on with &lt;code&gt;PYTHON_JIT=1&lt;/code&gt;). Libraries: numpy 2.5.4, pandas 3.0.6, pyarrow 26.0.0, all installed from &lt;code&gt;cp315&lt;/code&gt; wheels three days after the release. The VM is shared with other workloads, so I report medians of repeated fresh processes and repeat whole rounds.&lt;/p&gt;
&lt;p&gt;One thing I did not expect: the VM had uv 0.12.24, and &lt;code&gt;uv python install 3.15&lt;/code&gt; installed &lt;strong&gt;3.15.0rc3&lt;/strong&gt;, not the final release. uv 0.13.0 installed 3.15.0. If your images resolve &quot;3.15&quot; through an older tool, check &lt;code&gt;python -VV&lt;/code&gt; before you trust anything. That matters again in the profiler section.&lt;/p&gt;
&lt;h4 id=&quot;jit-off-vs-on&quot;&gt;JIT off vs on&lt;/h4&gt;
&lt;p&gt;Three workloads, timed in-process, 7 fresh processes per setting, two rounds:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Workload &lt;/th&gt;&lt;th&gt;JIT off (median) &lt;/th&gt;&lt;th&gt;JIT on (median) &lt;/th&gt;&lt;th&gt;Change&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Pure-Python parsing loop, 1M rows &lt;/td&gt;&lt;td&gt;337.6 / 332.4 ms &lt;/td&gt;&lt;td&gt;309.7 / 304.6 ms &lt;/td&gt;&lt;td&gt;about 8% faster&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;pandas row-wise &lt;code&gt;apply&lt;/code&gt;, 100k rows &lt;/td&gt;&lt;td&gt;1176.9 / 1183.6 ms &lt;/td&gt;&lt;td&gt;1126.8 / 1144.3 ms &lt;/td&gt;&lt;td&gt;about 3 to 4% faster&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;pandas vectorized &lt;code&gt;groupby().sum()&lt;/code&gt;, 2M rows &lt;/td&gt;&lt;td&gt;63.5 / 76.3 ms &lt;/td&gt;&lt;td&gt;63.3 / 71.4 ms &lt;/td&gt;&lt;td&gt;within noise (single runs ranged 60 to 103 ms)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;jit_bench.py (the three workloads)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&quot;&quot;&quot;Time three data-team-shaped workloads in-process. Run with PYTHON_JIT=0 or 1.&quot;&quot;&quot;
import sys
import time

import numpy as np
import pandas as pd


def pure_python_parse(n=1_000_000):
    total = 0
    for i in range(n):
        line = f&quot;{i},{i % 97},{i * 3}&quot;
        a, b, c = line.split(&quot;,&quot;)
        if int(b) &amp;gt; 50:
            total += int(c)
    return total


def vectorized_groupby(n=2_000_000):
    rng = np.random.default_rng(42)
    df = pd.DataFrame({&quot;k&quot;: rng.integers(0, 50_000, n), &quot;v&quot;: rng.random(n)})
    return df.groupby(&quot;k&quot;)[&quot;v&quot;].sum().iloc[0]


def rowwise_apply(n=100_000):
    df = pd.DataFrame({&quot;email&quot;: [f&quot; User{i}+*Emails are not allowed* &quot; for i in range(n)]})
    return df.apply(lambda r: r[&quot;email&quot;].strip().lower().replace(&quot;+spam&quot;, &quot;&quot;), axis=1).iloc[0]


WORKLOADS = {&quot;pure_python_parse&quot;: pure_python_parse,
             &quot;vectorized_groupby&quot;: vectorized_groupby,
             &quot;rowwise_apply&quot;: rowwise_apply}

fn = WORKLOADS[sys.argv[1]]
fn(10_000)  # warm-up
t0 = time.perf_counter()
fn()
print(f&quot;{(time.perf_counter() - t0) * 1000:.1f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run it as &lt;code&gt;PYTHON_JIT=0 python jit_bench.py pure_python_parse&lt;/code&gt; and &lt;code&gt;PYTHON_JIT=1 ...&lt;/code&gt;, several times each.&lt;/p&gt;
&lt;p&gt;What the numbers support (interpretation): the JIT does what the release notes say for Python-level code. The pure-Python loop landed right in the 7-8% band. But a pipeline whose hot path is already in numpy, pandas or Arrow does not get faster, because the JIT never sees that code. And the row-wise &lt;code&gt;apply&lt;/code&gt; is the honest middle: 3 to 4% is real, but the fix for a 1.2 second &lt;code&gt;apply&lt;/code&gt; is to vectorize it, not to wait for a JIT.&lt;/p&gt;
&lt;p&gt;The free-threading ABI is a packaging milestone. What's New says moving an extension to &lt;code&gt;abi3t&lt;/code&gt; &quot;usually requires some non-trivial changes to the source code&quot;, and it pays off only when the extensions you depend on ship those wheels and you run a free-threaded build (&lt;code&gt;cp315t&lt;/code&gt;). That is worth tracking, not something you collect by bumping a base image.&lt;/p&gt;
&lt;p&gt;So the antithesis is partly right: there is a free win for Python-level code. It is small, it is opt-in, and it is experimental. The three features below change your runbooks this week.&lt;/p&gt;
&lt;h3 id=&quot;1-lazy-imports-fix-startup-not-throughput&quot;&gt;1. Lazy imports fix startup, not throughput&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The wrong assumption:&lt;/strong&gt; &quot;lazy imports make Python faster.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The mechanics:&lt;/strong&gt; &lt;code&gt;lazy import pandas as pd&lt;/code&gt; binds &lt;code&gt;pd&lt;/code&gt; to a lightweight proxy. The module is loaded the first time the name is used, a step PEP 810 calls reification. If loading fails, the error is raised at first use, and the traceback shows both places. Lazy imports are only allowed at module scope: inside a function, a class body or a &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;except&lt;/code&gt; they are a &lt;code&gt;SyntaxError&lt;/code&gt;. I confirmed the &lt;code&gt;try&lt;/code&gt; case on 3.15.0, which rules out the classic &lt;code&gt;try: import orjson as json&lt;/code&gt; fallback.&lt;/p&gt;
&lt;p&gt;I wrote a toy pipeline CLI, &lt;code&gt;pipectl&lt;/code&gt;, with the imports most data tools have at the top: numpy, pandas, pyarrow, requests. It has a &lt;code&gt;version&lt;/code&gt; command, a &lt;code&gt;check&lt;/code&gt; command that validates a JSON config, and a &lt;code&gt;run&lt;/code&gt; command that aggregates 2M rows and writes Parquet. The lazy variant changes only the import lines:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;lazy import json
import argparse
import sys

lazy import numpy as np
lazy import pandas as pd
lazy import pyarrow as pa
lazy import pyarrow.parquet as pq
lazy import requests
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each command was run 30 times in a fresh process after warm-up, in three clean rounds:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Command &lt;/th&gt;&lt;th&gt;Eager (median per round) &lt;/th&gt;&lt;th&gt;Lazy (median per round)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;python -c pass&lt;/code&gt; (floor) &lt;/td&gt;&lt;td&gt;9.3 ms &lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pipectl version&lt;/code&gt; &lt;/td&gt;&lt;td&gt;351.4 / 381.2 / 344.4 ms &lt;/td&gt;&lt;td&gt;20.0 / 19.6 / 19.9 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pipectl check cfg.json&lt;/code&gt; &lt;/td&gt;&lt;td&gt;352.1 / 342.7 / 352.1 ms &lt;/td&gt;&lt;td&gt;21.2 / 21.1 / 20.8 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;pipectl run&lt;/code&gt; (2M rows) &lt;/td&gt;&lt;td&gt;453.2 / 441.0 / 450.8 ms &lt;/td&gt;&lt;td&gt;400.5 / 392.8 / 400.5 ms&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;-X importtime&lt;/code&gt; shows where the eager 350 ms goes: pandas about 180 ms cumulative, numpy about 40 ms, requests about 35 ms (medians of three runs, and importtime adds its own overhead).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;version&lt;/code&gt; and &lt;code&gt;check&lt;/code&gt; rows are the point of the feature: about 94% less wall time, because those commands never touch pandas. That is more than the &quot;50-70% in practice&quot; that &lt;a rel=&quot;nofollow&quot; href=&quot;https://peps.python.org/pep-0810/&quot;&gt;PEP 810&lt;/a&gt; cites for command-line tools, and it should be: my CLI is the extreme case where the cheap commands use none of the heavy imports.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;run&lt;/code&gt; row is the one to read twice. Lazy saved about 50 ms, so did it make the work faster? No. I ran a third variant, eager but with the &lt;code&gt;import requests&lt;/code&gt; line deleted:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;code&gt;pipectl run&lt;/code&gt;, 20 runs each &lt;/th&gt;&lt;th&gt;Median&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;eager &lt;/td&gt;&lt;td&gt;454.9 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;eager without &lt;code&gt;import requests&lt;/code&gt; &lt;/td&gt;&lt;td&gt;410.4 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;lazy &lt;/td&gt;&lt;td&gt;413.3 ms&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The whole gain is the import that the &lt;code&gt;run&lt;/code&gt; path never uses. &lt;strong&gt;Lazy imports remove the imports you don't use. They don't make the ones you do use any cheaper.&lt;/strong&gt; If your job imports pandas and uses pandas, it pays the full 180 ms either way.&lt;/p&gt;
&lt;p&gt;Where this pays off for data teams (interpretation): CLIs, &lt;code&gt;--help&lt;/code&gt; in CI, pre-commit hooks, tests that import a package to run one function, notebook kernels, and anything that gets parsed often. The Airflow docs already warn that &quot;import statements also count as top-level code&quot; in Dag files and that expensive imports there hurt the scheduler (&lt;a rel=&quot;nofollow&quot; href=&quot;https://airflow.apache.org/docs/apache-airflow/stable/best-practices.html#top-level-python-code&quot;&gt;Airflow best practices&lt;/a&gt;). The &lt;code&gt;lazy&lt;/code&gt; keyword looks built for that pattern. I did not test it inside Airflow, and you need an Airflow version that supports 3.15 first.&lt;/p&gt;
&lt;h4 id=&quot;trap-1-the-global-switch-broke-pandas&quot;&gt;Trap 1: the global switch broke pandas&lt;/h4&gt;
&lt;p&gt;3.15 also has an interpreter-wide mode: &lt;code&gt;-X lazy_imports=all&lt;/code&gt; or &lt;code&gt;PYTHON_LAZY_IMPORTS=all&lt;/code&gt; makes every module-level import in every module potentially lazy, third-party code included. It is tempting to flip it in a job image and collect the startup win without touching code. On 3.15.0 with pandas 3.0.6 and numpy 2.5.4, that failed 5 out of 5 times:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ PYTHON_LAZY_IMPORTS=all python -c &quot;import pandas as pd; pd.DataFrame({'a': [1]})&quot;
  File &quot;.../pandas/_typing.py&quot;, line 401, in &amp;lt;module&amp;gt;
    TakeIndexer: TypeAlias = Sequence[int] | Sequence[np.integer] | npt.NDArray[np.integer]
                                                                    ~~~~~~~~~~~^^^^^^^^^^^^
TypeError: 'lazy_import' object is not subscriptable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The minimal trigger is &lt;code&gt;npt.NDArray[np.integer]&lt;/code&gt; with &lt;code&gt;numpy.typing&lt;/code&gt; imported under the global mode. numpy alone and requests alone worked. A tiny pure-Python package with the same re-export chain worked too, so this looks specific to how &lt;code&gt;numpy.typing&lt;/code&gt; (which defines a module-level &lt;code&gt;__getattr__&lt;/code&gt;) interacts with global laziness. I did not chase it further. The lesson does not depend on the root cause: the global mode changes the import semantics of code you did not write, and the &lt;code&gt;lazy&lt;/code&gt; keyword in your own entry point does not.&lt;/p&gt;
&lt;h4 id=&quot;trap-2-side-effect-imports-silently-stop-happening&quot;&gt;Trap 2: side-effect imports silently stop happening&lt;/h4&gt;
&lt;p&gt;Plenty of data code imports a module only for what it does at import time: registering a pandas accessor, a SQLAlchemy dialect, a plugin. Linters flag these as unused, and they are the first thing someone will mark &lt;code&gt;lazy&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;lazy import pandas as pd
lazy import dq_accessors  # noqa: F401  (registers df.dq at import time)

df = pd.DataFrame({&quot;a&quot;: [1, None, 3], &quot;b&quot;: [None, None, 1]})
print(df.dq.null_ratio())
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AttributeError: 'DataFrame' object has no attribute 'dq'. Did you mean '.eq' instead of '.dq'?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The name &lt;code&gt;dq_accessors&lt;/code&gt; is never used, so it is never reified, and the registration never runs. The eager version prints &lt;code&gt;{'a': 0.333, 'b': 0.667}&lt;/code&gt;. PEP 810 says this directly: side effects are &quot;deferred until first use&quot;, and it recommends explicit initialization functions instead of import-time registration.&lt;/p&gt;
&lt;h4 id=&quot;trap-3-the-pep-s-kill-switch-does-not-exist-in-3-15-0&quot;&gt;Trap 3: the PEP's kill switch does not exist in 3.15.0&lt;/h4&gt;
&lt;p&gt;PEP 810 describes a third mode, &lt;code&gt;&quot;none&quot;&lt;/code&gt;, to force every import eager. The shipped 3.15.0 accepts only &lt;code&gt;normal&lt;/code&gt; and &lt;code&gt;all&lt;/code&gt;, which matches the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.python.org/3.15/library/sys.html&quot;&gt;sys docs&lt;/a&gt;. Copying the PEP is not harmless:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ PYTHON_LAZY_IMPORTS=none python -c pass
Fatal Python error: config_init_lazy_imports: PYTHON_LAZY_IMPORTS: invalid value; expected 'all' or 'normal'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Exit code 1, before any of your code runs. &lt;code&gt;sys.set_lazy_imports(&quot;none&quot;)&lt;/code&gt; raises &lt;code&gt;ValueError&lt;/code&gt;. The PEP page itself now says it is &quot;a historical document&quot;. If you want an emergency &quot;everything eager&quot; switch, a filter that returns &lt;code&gt;False&lt;/code&gt; does it (verified: &lt;code&gt;json&lt;/code&gt; loads at the &lt;code&gt;lazy import&lt;/code&gt; line):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import sys

sys.set_lazy_imports_filter(lambda importer, name, fromlist: False)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;runbook-lazy-imports&quot;&gt;Runbook: lazy imports&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Measure first: &lt;code&gt;python -X importtime -m yourtool --help 2&amp;gt; imports.txt&lt;/code&gt;, then sort by the cumulative column.&lt;/li&gt;
&lt;li&gt;Mark only the heavy imports in your own entry point &lt;code&gt;lazy&lt;/code&gt;. Leave third-party internals alone.&lt;/li&gt;
&lt;li&gt;Keep side-effect imports eager, or replace them with an explicit &lt;code&gt;register()&lt;/code&gt; call.&lt;/li&gt;
&lt;li&gt;Do not set &lt;code&gt;PYTHON_LAZY_IMPORTS=all&lt;/code&gt; in a job image unless the full test suite passes under it.&lt;/li&gt;
&lt;li&gt;If you must support 3.14 too, use &lt;code&gt;__lazy_modules__ = [&quot;pandas&quot;, ...]&lt;/code&gt; with normal &lt;code&gt;import&lt;/code&gt; statements. 3.15 treats those imports as lazy, and older versions just see a module variable and import eagerly (checked on 3.15.0 and 3.13.5 with &lt;code&gt;json&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;2-tachyon-attach-to-the-stuck-job-by-pid-and-read-the-error-rate&quot;&gt;2. Tachyon: attach to the stuck job by PID, and read the error rate&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The wrong assumption:&lt;/strong&gt; &quot;a sampling profiler is free and always right.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The mechanics:&lt;/strong&gt; &lt;code&gt;profiling.sampling&lt;/code&gt; (PEP 799, named Tachyon) reads the target process's memory from outside. It can &lt;code&gt;run&lt;/code&gt; a script, &lt;code&gt;attach&lt;/code&gt; to a PID for a time window, or &lt;code&gt;dump&lt;/code&gt; a single traceback-style snapshot of a live process. Output formats include pstats tables, collapsed stacks, flame graphs, Firefox Profiler and a live TUI. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.python.org/3.15/library/profiling.sampling.html&quot;&gt;docs&lt;/a&gt; list the constraints that matter in production:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Same Python minor version for profiler and target. If either side is a pre-release, both must be the exact same version.&lt;/li&gt;
&lt;li&gt;Permissions. On Linux, root, &lt;code&gt;CAP_SYS_PTRACE&lt;/code&gt;, or a Yama &lt;code&gt;ptrace_scope&lt;/code&gt; of 0. The docs note the default &lt;code&gt;ptrace_scope&lt;/code&gt; of 1 restricts ptrace to parent processes.&lt;/li&gt;
&lt;li&gt;Non-blocking mode (the default) never stops the target, but &quot;can occasionally produce incomplete or inconsistent stack traces&quot;. &lt;code&gt;--blocking&lt;/code&gt; pauses the target for each sample.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wrote the classic slow nightly job: a pandas row-wise &lt;code&gt;apply&lt;/code&gt; with a per-row Python function, looping forever, and pretended I did not know why it was slow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;slow_job.py&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&quot;&quot;&quot;A 'stuck' nightly job: a row-wise pandas apply that nobody profiled.&quot;&quot;&quot;
import time

import numpy as np
import pandas as pd


def normalize_email(row):
    return row[&quot;email&quot;].strip().lower().replace(&quot;+spam&quot;, &quot;&quot;)


def enrich(df):
    df[&quot;email_norm&quot;] = df.apply(normalize_email, axis=1)
    return df


def main():
    rng = np.random.default_rng(0)
    n = 200_000
    df = pd.DataFrame({
        &quot;id&quot;: np.arange(n),
        &quot;email&quot;: [f&quot; User{i}+*Emails are not allowed* &quot; for i in rng.integers(0, 10**6, n)],
    })
    while True:
        enrich(df.copy())
        time.sleep(0.01)


if __name__ == &quot;__main__&quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing you want during an incident is &quot;what is it doing right now&quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ python -m profiling.sampling dump --blocking 440208
Stack dump for PID 440208, thread 440208 (main thread, has GIL; most recent call last):
  File &quot;slow_job.py&quot;, line 31, in &amp;lt;module&amp;gt;
    main()
  File &quot;slow_job.py&quot;, line 26, in main
    enrich(df.copy())
  File &quot;slow_job.py&quot;, line 14, in enrich
    df[&quot;email_norm&quot;] = df.apply(normalize_email, axis=1)
  ...
  File &quot;slow_job.py&quot;, line 10, in normalize_email
    return row[&quot;email&quot;].strip().lower().replace(&quot;+spam&quot;, &quot;&quot;)
  File &quot;.venv/lib/python3.15/site-packages/pandas/core/series.py&quot;, line 959, in Series.__getitem__
    return self._get_value(key)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No restart, no code change, and the culprit is on screen. On this kernel there is no Yama module (&lt;code&gt;/proc/sys/kernel/yama/ptrace_scope&lt;/code&gt; does not exist), so attaching as the same unprivileged user just worked. On a host with the default &lt;code&gt;ptrace_scope&lt;/code&gt; of 1, plan for root or &lt;code&gt;CAP_SYS_PTRACE&lt;/code&gt; before the incident, not during it.&lt;/p&gt;
&lt;p&gt;Then the part I did not expect. Without &lt;code&gt;--blocking&lt;/code&gt;, &lt;code&gt;dump&lt;/code&gt; failed 8 times out of 20 with &lt;code&gt;RuntimeError: Failed to parse initial frame in chain&lt;/code&gt;. With &lt;code&gt;--blocking&lt;/code&gt; it succeeded 20 out of 20. For &lt;code&gt;attach&lt;/code&gt;, the profiler prints an &lt;code&gt;Error rate&lt;/code&gt; line, the percentage of samples it could not read. I measured it on a fixed workload (median of six &lt;code&gt;apply&lt;/code&gt; rounds per run, 1 kHz default rate, three repetitions):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Mode &lt;/th&gt;&lt;th&gt;Target round time &lt;/th&gt;&lt;th&gt;Error rate&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;No profiler &lt;/td&gt;&lt;td&gt;1170 / 1150 / 1186 ms &lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;attach&lt;/code&gt; (non-blocking, default) &lt;/td&gt;&lt;td&gt;1179 / 1180 / 1150 ms &lt;/td&gt;&lt;td&gt;67.8% / 62.0% / 63.7%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;attach --blocking&lt;/code&gt; &lt;/td&gt;&lt;td&gt;1606 / 1760 / 1657 ms &lt;/td&gt;&lt;td&gt;1.5% / 1.6% / 1.3%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both halves of the docs are true at once. Non-blocking mode had no overhead I could measure on the target. And on this VM, 62 to 68% of its samples were unreadable in this test (46 to 50% in two earlier 5-second runs against slow_job.py, 38% against a pure-Python recursive function). Blocking mode was clean, and it made the job 37 to 53% slower while attached.&lt;/p&gt;
&lt;p&gt;Interpretation: treat a non-blocking profile like a survey with a response rate. If 60% of samples failed, the remaining 40% may still point at the right function (here the row-wise &lt;code&gt;apply&lt;/code&gt; dominated either way), but check the error rate before you trust percentages. Use &lt;code&gt;dump --blocking&lt;/code&gt; for a one-shot snapshot, where a single pause costs nothing, and keep &lt;code&gt;--blocking&lt;/code&gt; profiles short on anything user-facing.&lt;/p&gt;
&lt;p&gt;And the version rule bit me right away. A job started on the 3.15.0rc3 that the older uv had installed could not be inspected from 3.15.0:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Can't attach to a pre-release Python interpreter from a process running a different Python version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From rc3 to rc3 it worked. Pin the profiler to the job image's exact interpreter, which in practice means running it from inside that image.&lt;/p&gt;
&lt;p&gt;The quieter observability change is &lt;a rel=&quot;nofollow&quot; href=&quot;https://peps.python.org/pep-0831/&quot;&gt;PEP 831&lt;/a&gt;: CPython is now built with frame pointers by default. What's New says this makes native stack unwinding &quot;faster and more reliable&quot; for system profilers, debuggers, crash analysis tools and eBPF-based tools. The uv build I used has &lt;code&gt;-fno-omit-frame-pointer&lt;/code&gt; in its &lt;code&gt;CFLAGS&lt;/code&gt;. The catch, in the release notes' words: &quot;A single native component built without frame pointers can break stack unwinding for the whole Python process.&quot; Your own Rust or C extensions need the same flags if you want mixed native and Python profiles.&lt;/p&gt;
&lt;h4 id=&quot;runbook-profiling-a-stuck-job&quot;&gt;Runbook: profiling a stuck job&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;python -m profiling.sampling dump --blocking PID&lt;/code&gt; first. One snapshot, one short pause.&lt;/li&gt;
&lt;li&gt;Then &lt;code&gt;python -m profiling.sampling attach -d 30 --flamegraph -o job.html PID&lt;/code&gt;, and read &lt;code&gt;Error rate&lt;/code&gt; before the flame graph.&lt;/li&gt;
&lt;li&gt;Run the profiler with the job's exact interpreter. A pre-release on either side means exact versions only.&lt;/li&gt;
&lt;li&gt;Decide now who is allowed &lt;code&gt;CAP_SYS_PTRACE&lt;/code&gt; on worker hosts, and where.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;3-pth-import-lines-become-start-files-auditable-not-safe&quot;&gt;3. &lt;code&gt;.pth&lt;/code&gt; import lines become &lt;code&gt;.start&lt;/code&gt; files: auditable, not safe&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The wrong assumption:&lt;/strong&gt; &quot;a package can only run code when I import it.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The mechanics:&lt;/strong&gt; at startup the &lt;code&gt;site&lt;/code&gt; module reads every &lt;code&gt;.pth&lt;/code&gt; file in site-packages. Lines are paths to add to &lt;code&gt;sys.path&lt;/code&gt;, except lines that start with &lt;code&gt;import&lt;/code&gt;, which are executed with &lt;code&gt;exec()&lt;/code&gt;. Every Python process in that environment runs them: &lt;code&gt;python -c pass&lt;/code&gt;, pip, pytest, the notebook kernel, the scheduler. PEP 829 calls this &quot;a broad attack surface&quot;.&lt;/p&gt;
&lt;p&gt;This is not theoretical. The advisory for the compromised litellm 1.82.7 and 1.82.8 releases (&lt;a rel=&quot;nofollow&quot; href=&quot;https://osv.dev/vulnerability/PYSEC-2026-2&quot;&gt;PYSEC-2026-2&lt;/a&gt;, published March 24, 2026) describes credential-harvesting malware, and its references point to a &lt;code&gt;litellm_init.pth&lt;/code&gt; file inside the 1.82.8 package.&lt;/p&gt;
&lt;p&gt;And it is not only malware. &lt;code&gt;setuptools&lt;/code&gt; 84.0.0, installed fresh on the VM, still ships &lt;code&gt;distutils-precedence.pth&lt;/code&gt;, whose single line starts with &lt;code&gt;import os; ...&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What &lt;a rel=&quot;nofollow&quot; href=&quot;https://peps.python.org/pep-0829/&quot;&gt;PEP 829&lt;/a&gt; and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.python.org/3.15/library/site.html&quot;&gt;site docs&lt;/a&gt; change in 3.15:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A new &lt;code&gt;name.start&lt;/code&gt; file lists entry points as &lt;code&gt;pkg.mod:callable&lt;/code&gt;. They run after all &lt;code&gt;.pth&lt;/code&gt; path lines are applied.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;name.start&lt;/code&gt; exists next to &lt;code&gt;name.pth&lt;/code&gt;, the &lt;code&gt;import&lt;/code&gt; lines in that &lt;code&gt;.pth&lt;/code&gt; are ignored.&lt;/li&gt;
&lt;li&gt;Without a matching &lt;code&gt;.start&lt;/code&gt;, &lt;code&gt;import&lt;/code&gt; lines still run in 3.15, and the deprecation message appears only with &lt;code&gt;-v&lt;/code&gt;. The docs say they will be &quot;silently ignored in Python 3.18 and 3.19&quot;, with a warning in 3.20.&lt;/li&gt;
&lt;li&gt;Errors on one line no longer abort the rest of the file. A failing entry point prints a traceback and startup continues.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On the VM, with a demo &lt;code&gt;zz_demo.pth&lt;/code&gt; containing a path line and an &lt;code&gt;import&lt;/code&gt; line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ python -c &quot;import sys; print('/tmp/extra_path_dir' in sys.path)&quot;
[zz_demo.pth] import line executed at startup
True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;python -v&lt;/code&gt; shows the new deprecation notice for both the demo file and setuptools' file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;import lines in .../site-packages/distutils-precedence.pth are deprecated, use entry points in a .../site-packages/distutils-precedence.start file instead.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After adding &lt;code&gt;zz_demo.start&lt;/code&gt; with one line, &lt;code&gt;zz_demo_boot:init&lt;/code&gt;, the &lt;code&gt;import&lt;/code&gt; line is suppressed and the entry point runs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;import lines in .../zz_demo.pth are suppressed due to matching .../zz_demo.start file.
Executing entry point: zz_demo_boot:init from .../zz_demo.start
[zz_demo.start] entry point zz_demo_boot:init called
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is the part to be precise about. A &lt;code&gt;.start&lt;/code&gt; file still runs code at every interpreter start. A malicious wheel can ship one just as easily as a &lt;code&gt;.pth&lt;/code&gt;. The PEP says so: &quot;The overall pre-start code execution attack surface is not eliminated by this PEP.&quot; What 3.15 gives you is a narrower, auditable shape: startup code is a named callable in an importable module, it goes through the import system so audit hooks (PEP 578) can see it, and &quot;you can tell by listing the files in the site-dir, exactly where arbitrary code execution occurs.&quot;&lt;/p&gt;
&lt;p&gt;That listing is the useful part, and you can do it today on any Python version:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;startup_audit.py (runs on Python 3.9+)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&quot;&quot;&quot;List everything that can run code at interpreter start in this environment.

Run it with the interpreter you want to audit:
    .venv/bin/python startup_audit.py
&quot;&quot;&quot;
import site
import sys
from pathlib import Path

dirs = list(dict.fromkeys(site.getsitepackages() + [site.getusersitepackages()]))
found = 0
for d in map(Path, dirs):
    if not d.is_dir():
        continue
    for f in sorted(d.glob(&quot;*.pth&quot;)):
        lines = f.read_text(encoding=&quot;utf-8-sig&quot;, errors=&quot;replace&quot;).splitlines()
        code = [l for l in lines if l.startswith((&quot;import &quot;, &quot;import\t&quot;))]
        if code:
            found += 1
            shadow = f.with_suffix(&quot;.start&quot;).exists()
            note = &quot;skipped on 3.15+ (matching .start), runs on older Pythons&quot; if shadow else &quot;RUNS on every start&quot;
            print(f&quot;{f}  [{note}]&quot;)
            for l in code:
                print(f&quot;    {l[:110]}&quot;)
    for f in sorted(d.glob(&quot;*.start&quot;)):
        found += 1
        print(f&quot;{f}  [.start entry points]&quot;)
        for l in f.read_text(encoding=&quot;utf-8-sig&quot;).splitlines():
            if l.strip() and not l.lstrip().startswith(&quot;#&quot;):
                print(f&quot;    {l.strip()}&quot;)
    for name in (&quot;sitecustomize.py&quot;, &quot;usercustomize.py&quot;):
        if (d / name).exists():
            found += 1
            print(f&quot;{d / name}  [runs on every start]&quot;)
print(f&quot;{sys.version.split()[0]}: {found} startup code hook(s) found&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output in the demo environment (with setuptools installed):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;.../site-packages/distutils-precedence.pth  [RUNS on every start]
    import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'local') == 'local'; enabled and __
.../site-packages/zz_demo.pth  [skipped on 3.15+ (matching .start), runs on older Pythons]
    import sys; sys.stderr.write(&quot;[zz_demo.pth] import line executed at startup\n&quot;)
.../site-packages/zz_demo.start  [.start entry points]
    zz_demo_boot:init
3.15.0: 3 startup code hook(s) found
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most &lt;code&gt;.pth&lt;/code&gt; files are harmless. An editable install of a src-layout package with setuptools wrote a path-only &lt;code&gt;.pth&lt;/code&gt; on the VM, with no code at all. The ones that execute code are few enough to put on an allowlist.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;python -S&lt;/code&gt; disables both mechanisms (I checked), but it also skips site-packages entirely, so it is a debugging tool, not a production setting.&lt;/p&gt;
&lt;h4 id=&quot;runbook-startup-code&quot;&gt;Runbook: startup code&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Run the audit in your image build and fail the build on any startup hook that is not on an allowlist.&lt;/li&gt;
&lt;li&gt;If you maintain a package with a &lt;code&gt;.pth&lt;/code&gt; &lt;code&gt;import&lt;/code&gt; line, move the code into a zero-argument function and add a matching &lt;code&gt;.start&lt;/code&gt; file. Keep the &lt;code&gt;.pth&lt;/code&gt; line as &lt;code&gt;import pkg.mod; pkg.mod.callable()&lt;/code&gt; while you still support 3.14.&lt;/li&gt;
&lt;li&gt;Treat a new &lt;code&gt;.start&lt;/code&gt; file in a dependency diff like a new &lt;code&gt;postinstall&lt;/code&gt; script in npm: a reason to read the release.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;what-to-do-this-week&quot;&gt;What to do this week&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;If you run... &lt;/th&gt;&lt;th&gt;Do this &lt;/th&gt;&lt;th&gt;Skip this&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Python CLIs, CI tooling, notebook kernels &lt;/td&gt;&lt;td&gt;Measure with &lt;code&gt;-X importtime&lt;/code&gt;, add &lt;code&gt;lazy&lt;/code&gt; to your entry point's heavy imports &lt;/td&gt;&lt;td&gt;&lt;code&gt;PYTHON_LAZY_IMPORTS=all&lt;/code&gt; in images&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Long batch jobs on VMs or Kubernetes &lt;/td&gt;&lt;td&gt;Install the exact job interpreter in the image, rehearse &lt;code&gt;dump --blocking&lt;/code&gt; and &lt;code&gt;attach&lt;/code&gt; on a staging job, sort out &lt;code&gt;CAP_SYS_PTRACE&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Trusting a profile without reading &lt;code&gt;Error rate&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Anything that installs from PyPI &lt;/td&gt;&lt;td&gt;Add the startup audit to the image build, review new &lt;code&gt;.pth&lt;/code&gt; and &lt;code&gt;.start&lt;/code&gt; files in dependency updates &lt;/td&gt;&lt;td&gt;Assuming &lt;code&gt;.start&lt;/code&gt; files make startup code safe&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Python-heavy loops or row-wise UDFs &lt;/td&gt;&lt;td&gt;Try &lt;code&gt;PYTHON_JIT=1&lt;/code&gt; in a benchmark, expect single digits &lt;/td&gt;&lt;td&gt;Counting on the JIT to fix a row-wise &lt;code&gt;apply&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The JIT will show up in your benchmarks as a few percent. Lazy imports, Tachyon and &lt;code&gt;.start&lt;/code&gt; files will show up in your incident timeline: the CLI that starts in 20 ms instead of 350, the stuck job you can inspect without a restart, and the list of files that run code before your &lt;code&gt;main()&lt;/code&gt; does. Upgrade for those, and measure the rest.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30776/python-3-15-is-not-a-speed-release-for-data-teams-it-is-an-operations-release</guid>
<pubDate>Mon, 12 Oct 2026 07:13:52 +0000</pubDate>
</item>
<item>
<title>Build a Pure CSS Radar Chart — Lightweight, Max Performance</title>
<link>https://coderlegion.com/30746/build-a-pure-css-radar-chart-lightweight-max-performance</link>
<description>&lt;p&gt;Most radar charts pull in SVG path math or a JavaScript charting library. You can skip both.&lt;/p&gt;
&lt;p&gt;With &lt;strong&gt;st-core.fscss&lt;/strong&gt;, a spider (radar) chart is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an FSCSS array of scores (0–100)&lt;/li&gt;
&lt;li&gt;polar coordinates stored as CSS variables (&lt;code&gt;--st-sxN&lt;/code&gt;, &lt;code&gt;--st-syN&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;shapes drawn with &lt;code&gt;clip-path: polygon()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;rings and spokes from gradients&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No canvas. No SVG. No chart runtime on the critical path.&lt;/p&gt;
&lt;p&gt;Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss&quot;&gt;github.com/fscss-ttr/st-core.fscss&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;here-s-how-it-works&quot;&gt;Here’s how it works&lt;/h3&gt;
&lt;h4 id=&quot;1-data-is-an-array&quot;&gt;1. Data is an array&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@arr skills[80, 65, 90, 55, 70, 85]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Length can be 5, 6, 8 — whatever you need. Geometry (number of polygon vertices) is fixed when FSCSS expands the mixins.&lt;/p&gt;
&lt;h4 id=&quot;2-one-mixin-writes-coordinates&quot;&gt;2. One mixin writes coordinates&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.spider {
  @st-spider-points(skills, 42)  /* array, max radius % of the box */
  position: relative;
  width: 280px;
  height: 280px;
  border-radius: 50%;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each index &lt;code&gt;i&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;angle = &lt;code&gt;(i - 1) × 360° / N&lt;/code&gt; (0° at the &lt;strong&gt;top&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;radius = &lt;code&gt;(value / 100) × rmax&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;CSS gets:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;--st-sxN: calc(50% + (r * 1%) * sin(θ));
--st-syN: calc(50% - (r * 1%) * cos(θ));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Trig stays in the browser’s &lt;code&gt;calc()&lt;/code&gt;. Updating values later is rewriting those variables.&lt;/p&gt;
&lt;h4 id=&quot;3-renderers-only-read-variables&quot;&gt;3. Renderers only read variables&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@st-spider-fill(.spider-fill, skills)
@st-spider-line(.spider-line, skills)
@st-spider-dots(.spider-dot-, skills, 9px)
@st-spider-grid(.spider-grid)
@st-spider-spokes(.spider-spokes, skills)
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;Role&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;fill&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Closed polygon through every &lt;code&gt;--st-sx/sy&lt;/code&gt; pair&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;line&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Dual-pass polygon (outer path + path scaled toward center) so the stroke has real thickness&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;dots&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Absolutely positioned markers at each vertex&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;grid&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Concentric rings (&lt;code&gt;radial-gradient&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;spokes&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Thin radial axes (&lt;code&gt;conic-gradient&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; call &lt;code&gt;@st-spider-points&lt;/code&gt; on the &lt;strong&gt;host&lt;/strong&gt;. Children inherit. Same model as linear &lt;code&gt;@st-chart-points&lt;/code&gt; / fill / line in st-core.&lt;/p&gt;
&lt;h4 id=&quot;4-markup-is-plain-html&quot;&gt;4. Markup is plain HTML&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;spider&quot;&amp;gt;
  &amp;lt;div class=&quot;spider-grid&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-spokes&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-fill&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-line&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-dot-1&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-dot-2&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-dot-3&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-dot-4&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-dot-5&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-dot-6&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dots need real nodes — the mixin emits &lt;code&gt;.spider-dot-1&lt;/code&gt; … &lt;code&gt;.spider-dot-N&lt;/code&gt; rules; it does not invent elements.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;here-s-how-values-can-be-displayed-on-the-dot-nodes&quot;&gt;Here’s how values can be displayed on the dot nodes&lt;/h3&gt;
&lt;p&gt;Markers are normal DOM nodes, so you can show the score next to (or inside) each dot without a chart API.&lt;/p&gt;
&lt;h4 id=&quot;option-a-data-value-css-after&quot;&gt;Option A — &lt;code&gt;data-value&lt;/code&gt; + CSS &lt;code&gt;::after&lt;/code&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;spider-dot-1&quot; data-value=&quot;80&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;div class=&quot;spider-dot-2&quot; data-value=&quot;65&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;!-- … --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.spider-dot-1,
.spider-dot-2,
.spider-dot-3,
.spider-dot-4,
.spider-dot-5,
.spider-dot-6 {
  /* st-core already sets left/top from --st-sx / --st-sy */
}

[data-value]::after {
  content: attr(data-value);
  position: absolute;
  left: 50%;
  top: -1.4em;
  transform: translateX(-50%);
  font-size: 0.65rem;
  font-variant-numeric: tabular-nums;
  color: var(--st-text);
  white-space: nowrap;
  pointer-events: none;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Push labels slightly outward per axis if they collide with the polygon (different &lt;code&gt;top&lt;/code&gt; / &lt;code&gt;left&lt;/code&gt; offsets per index).&lt;/p&gt;
&lt;h4 id=&quot;option-b-nested-label-element&quot;&gt;Option B — nested label element&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;spider-dot-1&quot;&amp;gt;
  &amp;lt;span class=&quot;dot-label&quot;&amp;gt;80&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.dot-label {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 4px);
  transform: translateX(-50%);
  font-size: 0.65rem;
  color: var(--st-muted);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;option-c-update-labels-from-js-with-the-series&quot;&gt;Option C — update labels from JS with the series&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function setSeries(host, values, rmax = 42) {
  const n = values.length;
  let css = &quot;&quot;;
  values.forEach((v, i) =&amp;gt; {
    const ang = (i * 360) / n;
    const r = (v / 100) * rmax;
    css += `--st-sx${i + 1}: calc(50% + (${r} * 1%) * sin(${ang} * 1deg));`;
    css += `--st-sy${i + 1}: calc(50% - (${r} * 1%) * cos(${ang} * 1deg));`;
    const dot = host.querySelector(`.spider-dot-${i + 1}`);
    if (dot) {
      dot.dataset.value = v;
      // or: dot.querySelector(&quot;.dot-label&quot;).textContent = v;
    }
  });
  host.style.cssText = css;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Geometry and labels stay in sync because both come from the same array pass.&lt;/p&gt;
&lt;p&gt;Axis names (Speed, Quality, …) stay outside the dots — absolute labels around the circle — so vertices stay readable at small sizes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;here-s-why-it-s-lighter&quot;&gt;Here’s why it’s lighter&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Traditional radar &lt;/th&gt;&lt;th&gt;st-core radar&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Chart.js / D3 / Recharts bundle &lt;/td&gt;&lt;td&gt;Mixins → CSS&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;SVG path string updates &lt;/td&gt;&lt;td&gt;Custom properties + &lt;code&gt;clip-path&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Layout + paint on every redraw &lt;/td&gt;&lt;td&gt;Often compositor-only if you transition &lt;code&gt;clip-path&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Library hydration before first paint &lt;/td&gt;&lt;td&gt;CSS paints as soon as the stylesheet applies&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Compile time:&lt;/strong&gt; FSCSS expands loops into concrete &lt;code&gt;polygon()&lt;/code&gt; stops and selectors. Array &lt;strong&gt;length&lt;/strong&gt; is fixed in the stylesheet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Runtime:&lt;/strong&gt; Changing scores is assigning &lt;code&gt;--st-sxN&lt;/code&gt; / &lt;code&gt;--st-syN&lt;/code&gt; (and optional label text). No path serializer, no virtual DOM for the shape.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Size:&lt;/strong&gt; A compiled spider block is on the order of &lt;strong&gt;~1 kb&lt;/strong&gt; of CSS for the chart rules — not a 50–200 kb chart dependency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Performance profile:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First paint: no JS required for a static series  &lt;/li&gt;
&lt;li&gt;Updates: style recalc + composite; avoid layout thrash if size is fixed  &lt;/li&gt;
&lt;li&gt;Multi-series: extra layers are extra divs + inherited variables, not extra chart instances&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s “max performance” in the practical sense: &lt;strong&gt;minimum work per frame and minimum bytes on the wire.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;here-are-some-use-cases-of-spider-radar-charts&quot;&gt;Here are some use cases of spider (radar) charts&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Skills / competency matrices&lt;/strong&gt; — Speed, Quality, UX, Scale, Cost, Support for a team or candidate.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product scorecards&lt;/strong&gt; — Compare features across dimensions (performance, price, ecosystem, docs, support).  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sprint or OKR health&lt;/strong&gt; — Delivery, quality, collaboration, predictability on one glanceable shape.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security / compliance posture&lt;/strong&gt; — Control families as axes; one polygon per system or vendor.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Player or character stats&lt;/strong&gt; — Games and education UIs where polar layout is the expected metaphor.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Brand or perception studies&lt;/strong&gt; — Trust, value, innovation, service — survey averages as radii.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Anywhere humans already think in “balanced vs spiky” profiles, a radar chart communicates faster than six separate bars.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;here-s-how-to-do-multi-series-radar-charts-with-still-pure-css&quot;&gt;Here’s how to do multi-series radar charts with still pure CSS&lt;/h3&gt;
&lt;p&gt;Each series is a &lt;strong&gt;separate host&lt;/strong&gt; with its own &lt;code&gt;@st-spider-points&lt;/code&gt;. Shared grid and spokes sit underneath.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from st-core@v2)
@st-root()
@st-spider-root()

@arr seriesA[70, 55, 40, 85, 90]
@arr seriesB[45, 75, 60, 50, 55]
@arr seriesC[85, 40, 70, 35, 65]

@st-spider-fill(.fill-a, seriesA)
@st-spider-line(.line-a, seriesA)
@st-spider-dots(.dot-a-, seriesA, 8px)

@st-spider-fill(.fill-b, seriesB)
@st-spider-line(.line-b, seriesB)
@st-spider-dots(.dot-b-, seriesB, 8px)

@st-spider-fill(.fill-c, seriesC)
@st-spider-line(.line-c, seriesC)
@st-spider-dots(.dot-c-, seriesC, 8px)

@st-spider-grid(.spider-grid)
@st-spider-spokes-n(.multi-spokes, 5)

.multi-spider {
  position: relative;
  width: 320px;
  height: 320px;
  border-radius: 50%;
  background: var(--st-surface);
}

.series-a {
  @st-spider-points(seriesA, 42)
  position: absolute;
  inset: 0;
  --st-accent: #ff9f43;
  --st-spider-fill-opacity: 20%;
}
.series-b {
  @st-spider-points(seriesB, 42)
  position: absolute;
  inset: 0;
  --st-accent: #54a0ff;
  --st-spider-fill-opacity: 16%;
}
.series-c {
  @st-spider-points(seriesC, 42)
  position: absolute;
  inset: 0;
  --st-accent: #ff6b9d;
  --st-spider-fill-opacity: 18%;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;multi-spider&quot;&amp;gt;
  &amp;lt;div class=&quot;spider-grid&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;multi-spokes&quot;&amp;gt;&amp;lt;/div&amp;gt;

  &amp;lt;div class=&quot;series-a&quot;&amp;gt;
    &amp;lt;div class=&quot;fill-a&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;line-a&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-a-1&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-a-2&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-a-3&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-a-4&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-a-5&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class=&quot;series-b&quot;&amp;gt;
    &amp;lt;div class=&quot;fill-b&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;line-b&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-b-1&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;!-- … --&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class=&quot;series-c&quot;&amp;gt;
    &amp;lt;div class=&quot;fill-c&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;line-c&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-c-1&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;!-- … --&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why this stays pure CSS:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No JS chart instance per series  &lt;/li&gt;
&lt;li&gt;Isolation is DOM + custom properties, not a scene graph  &lt;/li&gt;
&lt;li&gt;Opacity and accent are tokens (&lt;code&gt;--st-spider-fill-opacity&lt;/code&gt;, &lt;code&gt;--st-accent&lt;/code&gt;)  &lt;/li&gt;
&lt;li&gt;Spoke count can match the axis count via &lt;code&gt;@st-spider-spokes-n(..., 5)&lt;/code&gt; without tying spokes to one series’ array&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If two series shared one host and only one &lt;code&gt;@st-spider-points&lt;/code&gt; call, the last write would win and every series would draw the same shape. Separate hosts fix that — the same multi-series rule as linear st-core charts.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;quick-start&quot;&gt;Quick start&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;FSCSS &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Figsh/xfscss&quot;&gt;CLI&lt;/a&gt;, Vscode &lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=Figsh.fscss&quot;&gt;extention&lt;/a&gt;, or prototyping via its &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/&quot;&gt;cdn&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from st-core@v2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full API in the repo: &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss&quot;&gt;github.com/fscss-ttr/st-core.fscss&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Pure CSS radar charts are not a compromise. With st-core they’re a &lt;strong&gt;styling problem&lt;/strong&gt;: arrays in, variables out, polygons on screen — lightweight by construction, fast by default.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/30746/build-a-pure-css-radar-chart-lightweight-max-performance</guid>
<pubDate>Sun, 11 Oct 2026 20:07:46 +0000</pubDate>
</item>
<item>
<title>🔥 30 Days of Frontend — Day 4: Memoization in React</title>
<link>https://coderlegion.com/30728/30-days-of-frontend-day-4-memoization-in-react</link>
<description>&lt;p&gt;Have you ever looked at a React component and thought:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“Maybe I should wrap this function with &lt;code&gt;useCallback&lt;/code&gt;, this calculation with &lt;code&gt;useMemo&lt;/code&gt;, and the component with &lt;code&gt;React.memo&lt;/code&gt;?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You're not alone.&lt;/p&gt;
&lt;p&gt;Memoization is one of the most frequently discussed React performance techniques. But it's also one of the easiest to misuse.&lt;/p&gt;
&lt;p&gt;You can add memoization to an application, introduce more complexity, and still fail to improve its performance.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Because memoization is not about preventing every render. It's about avoiding work that doesn't need to happen again.&lt;/p&gt;
&lt;p&gt;Let's understand how it works, when it helps, and when it becomes unnecessary complexity.&lt;/p&gt;
&lt;h3 id=&quot;1-what-is-memoization-&quot;&gt;1. What Is Memoization?&lt;/h3&gt;
&lt;p&gt;Memoization is an optimization technique that caches the result of a computation so it can be reused when the relevant inputs haven't changed.&lt;/p&gt;
&lt;p&gt;Imagine you have an expensive function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;function calculateTotalPrice(products: Product[]) {
  console.log(&quot;Calculating total price...&quot;);

  return products.reduce(
    (total, product) =&amp;gt; total + product.price,
    0
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every time you call this function, it calculates the total again.&lt;/p&gt;
&lt;p&gt;If the product list hasn't changed, repeating the same calculation may be unnecessary.&lt;/p&gt;
&lt;p&gt;Memoization lets us reuse a previous result when the inputs are still valid.&lt;/p&gt;
&lt;p&gt;Conceptually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Inputs
  |
  v
Have relevant inputs changed?
  |
  +---- Yes ----&amp;gt; Recalculate and cache the result
  |
  +---- No -----&amp;gt; Reuse the cached result
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React provides three commonly used memoization APIs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;useMemo&lt;/code&gt; — caches a calculated value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useCallback&lt;/code&gt; — caches a function reference.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;React.memo&lt;/code&gt; — can skip a component render when its props are unchanged.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They solve different problems, so they shouldn't be treated as interchangeable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-usememo-cache-an-expensive-calculation&quot;&gt;2. &lt;code&gt;useMemo&lt;/code&gt;: Cache an Expensive Calculation&lt;/h3&gt;
&lt;p&gt;Consider a product dashboard containing thousands of products.&lt;/p&gt;
&lt;p&gt;You need to filter products by category and calculate which products match a search query.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;function ProductList({
  products,
  search,
}: {
  products: Product[];
  search: string;
}) {
  const filteredProducts = products.filter((product) =&amp;gt;
    product.name.toLowerCase().includes(search.toLowerCase())
  );

  return (
    &amp;lt;ul&amp;gt;
      {filteredProducts.map((product) =&amp;gt; (
        &amp;lt;li key={product.id}&amp;gt;{product.name}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The filtering operation runs whenever &lt;code&gt;ProductList&lt;/code&gt; renders.&lt;/p&gt;
&lt;p&gt;That may be perfectly fine for a small list. But imagine the filtering logic is expensive and the component also renders because some unrelated state changes.&lt;/p&gt;
&lt;p&gt;We can cache the calculated array with &lt;code&gt;useMemo&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { useMemo } from &quot;react&quot;;

function ProductList({
  products,
  search,
}: {
  products: Product[];
  search: string;
}) {
  const filteredProducts = useMemo(() =&amp;gt; {
    return products.filter((product) =&amp;gt;
      product.name.toLowerCase().includes(search.toLowerCase())
    );
  }, [products, search]);

  return (
    &amp;lt;ul&amp;gt;
      {filteredProducts.map((product) =&amp;gt; (
        &amp;lt;li key={product.id}&amp;gt;{product.name}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;what-changed-&quot;&gt;What changed?&lt;/h4&gt;
&lt;p&gt;React recalculates &lt;code&gt;filteredProducts&lt;/code&gt; when &lt;code&gt;products&lt;/code&gt; or &lt;code&gt;search&lt;/code&gt; changes.&lt;/p&gt;
&lt;p&gt;If the component renders again while both dependencies remain unchanged, React can reuse the previous result.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Initial render
  └── products + search
        └── Calculate filtered products

Unrelated state changes
  └── products + search unchanged
        └── Reuse cached result

Search changes
  └── New search value
        └── Calculate filtered products again
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;an-important-limitation&quot;&gt;An important limitation&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useMemo&lt;/code&gt; does not make every search update faster.&lt;/p&gt;
&lt;p&gt;When the user types another character, &lt;code&gt;search&lt;/code&gt; changes. The filtering calculation must run again to produce the correct results.&lt;/p&gt;
&lt;p&gt;Memoization is most useful when a calculation is expensive and its dependencies remain unchanged across renders.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-usecallback-cache-a-function-reference&quot;&gt;3. &lt;code&gt;useCallback&lt;/code&gt;: Cache a Function Reference&lt;/h3&gt;
&lt;p&gt;Now let's look at &lt;code&gt;useCallback&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Consider a parent component that passes an event handler to a child:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;function ProductPage() {
  const [count, setCount] = useState(0);

  const handleAddToCart = (productId: number) =&amp;gt; {
    console.log(&quot;Added product:&quot;, productId);
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;
        Count: {count}
      &amp;lt;/button&amp;gt;

      &amp;lt;ProductCard onAddToCart={handleAddToCart} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever &lt;code&gt;ProductPage&lt;/code&gt; renders, the function expression creates a new function reference.&lt;/p&gt;
&lt;p&gt;Even though the function's logic hasn't changed, the reference is different.&lt;/p&gt;
&lt;p&gt;That matters if &lt;code&gt;ProductCard&lt;/code&gt; is memoized and receives the handler as a prop.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const ProductCard = React.memo(function ProductCard({
  onAddToCart,
}: {
  onAddToCart: (productId: number) =&amp;gt; void;
}) {
  console.log(&quot;ProductCard rendered&quot;);

  return (
    &amp;lt;button onClick={() =&amp;gt; onAddToCart(42)}&amp;gt;
      Add to cart
    &amp;lt;/button&amp;gt;
  );
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, &lt;code&gt;React.memo&lt;/code&gt; compares props. A new function reference means the handler prop is considered changed, so the child may render again.&lt;/p&gt;
&lt;p&gt;We can stabilize the function reference with &lt;code&gt;useCallback&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { useCallback, useState } from &quot;react&quot;;

function ProductPage() {
  const [count, setCount] = useState(0);

  const handleAddToCart = useCallback((productId: number) =&amp;gt; {
    console.log(&quot;Added product:&quot;, productId);
  }, []);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;
        Count: {count}
      &amp;lt;/button&amp;gt;

      &amp;lt;ProductCard onAddToCart={handleAddToCart} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the callback doesn't depend on any reactive values from the component, its dependency array is empty.&lt;/p&gt;
&lt;p&gt;Now, when &lt;code&gt;count&lt;/code&gt; changes, React can reuse the same callback reference. That allows the memoized &lt;code&gt;ProductCard&lt;/code&gt; to skip rendering if its other props also remain unchanged.&lt;/p&gt;
&lt;h4 id=&quot;what-about-dependencies-&quot;&gt;What about dependencies?&lt;/h4&gt;
&lt;p&gt;Consider this callback:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const handleAddToCart = useCallback(() =&amp;gt; {
  setCartCount(cartCount + 1);
}, [cartCount]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The callback depends on &lt;code&gt;cartCount&lt;/code&gt;, so its reference changes whenever &lt;code&gt;cartCount&lt;/code&gt; changes.&lt;/p&gt;
&lt;p&gt;For state updates that depend on the previous state, a functional update can sometimes avoid that dependency:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const handleAddToCart = useCallback(() =&amp;gt; {
  setCartCount((currentCount) =&amp;gt; currentCount + 1);
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works because the updater receives the latest state value.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; &lt;code&gt;useCallback&lt;/code&gt; caches a function reference; it doesn't make the function's execution inherently faster.&lt;/p&gt;
&lt;p&gt;If you're not passing the function to a memoized child or using it in another dependency-sensitive context, &lt;code&gt;useCallback&lt;/code&gt; may provide no meaningful benefit.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-react-memo-skip-unnecessary-component-renders&quot;&gt;4. &lt;code&gt;React.memo&lt;/code&gt;: Skip Unnecessary Component Renders&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;React.memo&lt;/code&gt; is different from the two hooks.&lt;/p&gt;
&lt;p&gt;It wraps a component and lets React skip rendering it when its props are unchanged, subject to React's normal rendering behavior.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const ProductCard = React.memo(function ProductCard({
  name,
  price,
}: {
  name: string;
  price: number;
}) {
  console.log(&quot;Rendering ProductCard:&quot;, name);

  return (
    &amp;lt;article&amp;gt;
      &amp;lt;h3&amp;gt;{name}&amp;lt;/h3&amp;gt;
      &amp;lt;p&amp;gt;${price}&amp;lt;/p&amp;gt;
    &amp;lt;/article&amp;gt;
  );
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Suppose the parent updates a separate counter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;function ProductPage() {
  const [count, setCount] = useState(0);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount((current) =&amp;gt; current + 1)}&amp;gt;
        Count: {count}
      &amp;lt;/button&amp;gt;

      &amp;lt;ProductCard name=&quot;Keyboard&quot; price={120} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The parent renders when the counter changes, but the &lt;code&gt;ProductCard&lt;/code&gt; props remain the same.&lt;/p&gt;
&lt;p&gt;React can skip rendering &lt;code&gt;ProductCard&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, &lt;code&gt;React.memo&lt;/code&gt; is not an absolute rendering lock. The component can still render because of its own state changes, consumed context changes, or other React behavior.&lt;/p&gt;
&lt;h4 id=&quot;a-common-mistake-unstable-object-props&quot;&gt;A common mistake: unstable object props&lt;/h4&gt;
&lt;p&gt;Consider this example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;ProductCard
  product={{
    id: 1,
    name: &quot;Keyboard&quot;,
    price: 120,
  }}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A new object is created whenever the parent renders.&lt;/p&gt;
&lt;p&gt;Even if the object contains the same data, its reference is different.&lt;/p&gt;
&lt;p&gt;Consequently, a memoized child can still render.&lt;/p&gt;
&lt;p&gt;This is why understanding referential equality matters when optimizing React applications.&lt;/p&gt;
&lt;p&gt;Two objects can contain identical properties and still be different references:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const first = { name: &quot;Keyboard&quot; };
const second = { name: &quot;Keyboard&quot; };

console.log(first === second); // false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Memoization works best when the values that cross component boundaries have the stability needed for the optimization to be effective.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-how-these-three-apis-work-together&quot;&gt;5. How These Three APIs Work Together&lt;/h3&gt;
&lt;p&gt;Let's put the concepts together.&lt;/p&gt;
&lt;p&gt;Imagine a dashboard with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A product search field&lt;/li&gt;
&lt;li&gt;A product list&lt;/li&gt;
&lt;li&gt;A shopping cart counter&lt;/li&gt;
&lt;li&gt;Expensive product filtering&lt;/li&gt;
&lt;li&gt;Memoized product cards&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A possible optimization strategy is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ProductPage
    |
    ├── useCallback
    |      └── Stabilize a handler passed to a memoized child
    |
    ├── useMemo
    |      └── Cache filtered products when dependencies are unchanged
    |
    └── ProductList
           |
           └── React.memo
                  └── Skip rendering when props are unchanged
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The APIs address different sources of repeated work:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;API &lt;/th&gt;&lt;th&gt;What it caches or optimizes &lt;/th&gt;&lt;th&gt;Useful when&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;useMemo&lt;/code&gt; &lt;/td&gt;&lt;td&gt;A calculated value &lt;/td&gt;&lt;td&gt;A calculation is expensive and dependencies often stay unchanged&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;useCallback&lt;/code&gt; &lt;/td&gt;&lt;td&gt;A function reference &lt;/td&gt;&lt;td&gt;Stable identity helps a memoized child or dependency-sensitive hook&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;React.memo&lt;/code&gt; &lt;/td&gt;&lt;td&gt;A component's render work based on props &lt;/td&gt;&lt;td&gt;A component renders often with unchanged props and rendering is costly&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The key is not to use all three together automatically. Use only the optimization that addresses the measured bottleneck.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-when-memoization-doesn-t-help&quot;&gt;6. When Memoization Doesn't Help&lt;/h3&gt;
&lt;p&gt;Memoization has costs too.&lt;/p&gt;
&lt;p&gt;It adds some combination of dependency tracking, comparisons, cached values, and code complexity.&lt;/p&gt;
&lt;p&gt;Consider this example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const fullName = useMemo(() =&amp;gt; {
  return `${firstName} ${lastName}`;
}, [firstName, lastName]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unless there is a specific reason to cache this value, the direct version is simpler:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const fullName = `${firstName} ${lastName}`;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Combining two strings is inexpensive. Memoizing it is unlikely to produce a measurable improvement.&lt;/p&gt;
&lt;p&gt;Another example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const handleClick = useCallback(() =&amp;gt; {
  console.log(&quot;Clicked&quot;);
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If this function is used only by a regular button in the same component, stabilizing its identity may not improve anything.&lt;/p&gt;
&lt;p&gt;The lesson is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not every calculation is expensive, not every render is a problem, and not every new function reference matters.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-react-compiler-changes-the-conversation&quot;&gt;7. React Compiler Changes the Conversation&lt;/h3&gt;
&lt;p&gt;Modern React projects may use the React Compiler, which can automatically memoize components, values, and functions when it can safely do so.&lt;/p&gt;
&lt;p&gt;This means manually adding &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, and &lt;code&gt;React.memo&lt;/code&gt; everywhere is less necessary in compiler-enabled applications.&lt;/p&gt;
&lt;p&gt;However, the compiler does not eliminate the need to understand performance.&lt;/p&gt;
&lt;p&gt;It cannot make every expensive algorithm cheap, remove the cost of rendering thousands of unnecessary elements, or guarantee that a slow interaction will become fast.&lt;/p&gt;
&lt;p&gt;You should still understand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What triggers a render&lt;/li&gt;
&lt;li&gt;How props and references change&lt;/li&gt;
&lt;li&gt;Which calculations are expensive&lt;/li&gt;
&lt;li&gt;When state should live closer to the components that use it&lt;/li&gt;
&lt;li&gt;How to measure actual rendering performance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your project uses React Compiler, follow its guidance and avoid adding manual memoization without a reason. Manual memoization can still be useful in appropriate cases, but it shouldn't be automatic boilerplate.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;8-final-takeaway&quot;&gt;8. Final Takeaway&lt;/h3&gt;
&lt;p&gt;Memoization is a tool, not a coding style, and the goal isn't to eliminate every render, the goal is to make the application responsive while keeping the code maintainable.&lt;/p&gt;
&lt;p&gt;My preferred performance workflow remains:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Measure → Identify the bottleneck → Optimize → Measure again.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Don't optimize because a component rendered, optimize because you've found work that is unnecessarily expensive.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/30728/30-days-of-frontend-day-4-memoization-in-react</guid>
<pubDate>Sun, 11 Oct 2026 17:45:13 +0000</pubDate>
</item>
<item>
<title>The Magic of Branchless in FinTech: How to Tame the NASD 30/360 Financial Standard and Solve the IRR Equation Without a Single IF</title>
<link>https://coderlegion.com/30680/the-magic-branchless-fintech-tame-nasd-financial-standard-solve-equation-without-single</link>
<description>&lt;p&gt;Calendar and interest calculations are a hidden &quot;landmine&quot; of the global financial system. Regulators and financiers have been unable to devise a simple date-calculation formula for several centuries. The US 30/360 NASD standard has been criticized multiple times and rewritten by various countries, while the built-in functions of Excel are completely frozen with February bugs, breaking the continuity and monotonicity of time at month boundaries.&lt;/p&gt;
&lt;p&gt;The production-grade programmatic core, source files, and clean VBA modules for this framework are officially published and open-sourced on my GitHub repository: [GitHub: branchless-continuous-time-irr].&lt;/p&gt;
&lt;p&gt;This article performs a complete mathematical and hardware deconstruction of this chaos. I will guide the reader through a rigorously verified chain of branchless discoveries: from computing the commercial NASD 30/360 standard to the complex mathematical framework that forms the basis of my peer-reviewed scientific paper published by Lomonosov Moscow State University, available for verification via direct link: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.elibrary.ru/item.asp?id=49514570&quot;&gt;https://www.elibrary.ru/item.asp?id=49514570&lt;/a&gt; . &lt;/p&gt;
&lt;p&gt;As an independent researcher, alumnus of Gubkin University, and the primary author of the accompanying fundamental research registered in the international scientific repository in Geneva (CERN Zenodo, DOI: 10.5281/zenodo.5007170, direct link: &lt;a rel=&quot;nofollow&quot; href=&quot;https://zenodo.org/record/5007170&quot;&gt;https://zenodo.org/record/5007170&lt;/a&gt;) and on the eLIBRARY platform (direct link: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.elibrary.ru/item.asp?id=46584125&quot;&gt;https://www.elibrary.ru/item.asp?id=46584125&lt;/a&gt;), I will deconstruct the integer calendar core with a precision limit up to the year 487,621, and detail a highly successful solution already developed by me within realistic operational environments that accomplished the impossible: differentiating the non-differentiable.&lt;/p&gt;
&lt;p&gt;To avoid superficial insinuation regarding the demonstration environment, I emphasize 4 strategic reasons why this algorithm is implemented strictly via pure Excel and VBA:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1. Universal Accessibility and Verification&lt;/strong&gt;: Microsoft Excel is installed on the workstation of every quantitative analyst, financier, and systems engineer globally. Verification of the algorithm requires no heavy IDE deployments or compiler configurations — the formula is pasted directly into a spreadsheet cell and instantly computes half a million years of astronomical time with 100% precision.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2. A Living Pseudocode&lt;/strong&gt;: The concise syntax of Excel cells and VBA provides absolute transparency. Stripped of platform-dependent syntactic sugar, this layout acts as a universal mathematical invariant that any engineer can effortlessly port line-by-line into C, Rust, Go, Python, Java or bare-metal Verilog/VHDL description layers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3. Maximum Portability and Accessibility&lt;/strong&gt;: Given that logical operations in VBA function exclusively as bitwise operations, I deliberately excluded all idiosyncratic features of this specific language from the codebase. The algorithm is engineered to be fully platform-agnostic, first to guarantee an entirely trivial transfer process into absolutely any other programming language, and second to remain intuitive to a wide audience who may not possess highly specialized software engineering credentials.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4. The Ideal Invariant for Silicon Synthesis&lt;/strong&gt;: The reality that this framework runs equally fast and with absolute accuracy even within an Excel/VBA scope utilizing a standard 32-bit &lt;code&gt;Long&lt;/code&gt; datatype without a single logical conditional expression holds the highest priority for hardware professionals. This demonstrates that synthesizing and baking such FinTech compute engines directly into hardware layers (at the level of FPGA / ASIC microchips) is a fundamentally simple and trivial engineering task.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-1-the-core-milestone-branchless-computation-of-the-nasd-30-360-standard&quot;&gt;Part 1. The Core Milestone: Branchless Computation of the NASD 30/360 Standard&lt;/h3&gt;
&lt;p&gt;The 30/360 NASD standard was originally introduced to serve two distinct objectives:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;To eliminate the irregularity of months and years using elementary formulas.&lt;/li&gt;
&lt;li&gt;To ensure that time intervals between dates separated by an exact integer number of months via the &lt;code&gt;EDATE&lt;/code&gt; methodology always yielded precisely: months / 12.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Neither task has been resolved over centuries of application, perpetually triggering endless legal and commercial disputes. I implement the core mechanics of the NASD standard as &lt;strong&gt;pure, end-to-end branchless arithmetic&lt;/strong&gt;. Conditional &lt;code&gt;IF-ELSE&lt;/code&gt; bifurcations are no longer required; the NASD logic maps seamlessly onto the mathematical axis using basic Boolean algebra (where TRUE = 1, FALSE = 0).&lt;/p&gt;
&lt;p&gt;If cell &lt;strong&gt;A1&lt;/strong&gt; contains the start date (D₁) and cell &lt;strong&gt;B1&lt;/strong&gt; contains the end date (D₂), the following unified analytical formula evaluates the precise financial interval under the NASD 30/360 convention without a single conditional &lt;code&gt;IF&lt;/code&gt; operator:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-excel&quot;&gt;=YEAR(B1)-YEAR(A1)+((DATE(YEAR(B1),3,0)=B1)*((DAY(A1)&amp;gt;29)+(DAY(B1)=28)*(DAY(A1)&amp;gt;28))+DAY(B1)-(DAY(B1)=31)-DAY(A1)+(DAY(A1)=31)+(MONTH(B1)-MONTH(A1))*30)/360
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The Branchless Compensation Mechanics:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-(DAY(B1)=31)&lt;/code&gt; and &lt;code&gt;+(DAY(A1)=31)&lt;/code&gt;: The logical equality returns &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt;. If the day coordinate is 31, the chronological weight of the date is automatically adjusted to the 30th day arithmetically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &quot;Zero March&quot; Trigger (&lt;code&gt;DATE(YEAR(B1),3,0)&lt;/code&gt;)&lt;/strong&gt;: In spreadsheet engine logic, this configuration (March 0) evaluates to the terminal day of February for the respective year (day 28 or 29). Multiplying this structural trigger by the weight coefficients &lt;code&gt;(DAY(A1)&amp;gt;29)&lt;/code&gt; and &lt;code&gt;(DAY(B1)=28)*(DAY(A1)&amp;gt;28)&lt;/code&gt; arithmetically Hack-neutralizes the non-linear operational shifts of February.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-2-the-foundation-a-branchless-calendar-decoder-normalization-and-stress-testing-the-getdays-y-m-d-core&quot;&gt;Part 2. The Foundation: A Branchless Calendar Decoder, Normalization, and Stress-Testing the &lt;code&gt;GetDays(y, m, d)&lt;/code&gt; Core&lt;/h3&gt;
&lt;p&gt;To feed the NASD equation and surrounding analytical operations with flawlessly structured data, a high-speed mechanism is required to translate absolute serial day integers back into a Year/Month/Day layout without logical branching steps.&lt;/p&gt;
&lt;p&gt;I deployed an analytical method of &lt;strong&gt;integer approximations of real periodic variations&lt;/strong&gt; utilizing tailored numerical constants (derived from the theory of continued fractions). The foundational paradigm shift relies on &lt;strong&gt;shifting the chronological epoch anchor to March 3rd&lt;/strong&gt;. This repositions the erratic boundary behavior of February to the absolute end of the annual compute cycle, converting a volatile Gregorian month sequence into a smooth, predictable mathematical continuum.&lt;/p&gt;
&lt;p&gt;Below is the original integer branchless engine written in universal standard VBA syntax (&lt;code&gt;Long&lt;/code&gt;), containing the function &lt;code&gt;GetDays(y, m, d)&lt;/code&gt; developed by me:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-vba&quot;&gt;Function DecodeYearMonthDay(ByVal G As Long) As Long()
  Dim y400 As Long, y100 As Long, y4 As Long, y As Long, m As Long
  G = G - 3
  y400 = Int(G / 146097)
  G = G - y400 * 146097
  y100 = Int((G + 1) * 10 / 365243)
  G = G - y100 * 36524
  y4 = Int(G / 1461)
  G = G - y4 * 1461
  y = Int((G + 1) * 10 / 3653)
  G = G - y * 365
  m = Int((G * 10 + 5) / 306)
  
  Dim numbers(5) As Long
  numbers(0) = y400
  numbers(1) = y100
  numbers(2) = y4
  numbers(3) = y
  numbers(4) = m
  numbers(5) = G
  DecodeYearMonthDay = numbers
End Function

Function GetYear(ByVal G As Long) As Long
  Dim a() As Long
  a = DecodeYearMonthDay(G)
  GetYear = 400 * a(0) + 100 * a(1) + 4 * a(2) + a(3) + Int((a(4) + 2) / 12)
End Function

Function GetMonth(ByVal G As Long) As Long
  Dim a() As Long
  a = DecodeYearMonthDay(G)
  GetMonth = ((a(4) + 2) Mod 12) + 1
End Function

Function GetDay(ByVal G As Long) As Long
  Dim a() As Long
  a = DecodeYearMonthDay(G)
  GetDay = a(5) - Int((306 * a(4) - 5) / 10)
End Function

Function GetDays(ByVal y As Long, ByVal m As Long, ByVal d As Long) As Long
  Dim my As Long
  my = m + y * 12 - 3
  GetDays = d + Int((367 * my + 31) / 12) - 2 * Int(my / 12) + Int(my / 48) - Int(my / 1200) + Int(my / 4800)
End Function

Function GetWeekDay(ByVal y As Long, ByVal m As Long, ByVal d As Long) As Long
  Dim my As Long
  my = (m + y * 12 - 3) Mod 4800
  GetWeekDay = (d - Int(my / 1200) + Int(my / 48) - 2 * Int(my / 12) + Int(31 * (my + 1) / 12)) Mod 7
End Function
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;native-input-data-normalization-within-the-getdays-core&quot;&gt;Native Input Data Normalization Within the &lt;code&gt;GetDays&lt;/code&gt; Core&lt;/h4&gt;
&lt;p&gt;An exceptional asset of the engineered &lt;code&gt;GetDays(y, m, d)&lt;/code&gt; algorithm is its &lt;strong&gt;absolute mathematical parity with native spreadsheet DATE evaluations&lt;/strong&gt;, yet achieved completely independent of conditional validation blocks. It accepts out-of-bounds, zero, or negative constraints natively — the underlying polynomial matrix performs an immediate, hidden normalization across time coordinates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Executing &lt;code&gt;GetDays(2024, 3, 0)&lt;/code&gt; (March 0) smoothly maps onto the real absolute index of &lt;strong&gt;February 29, 2024&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Executing &lt;code&gt;GetDays(2023, 1, -10)&lt;/code&gt; dynamically decrements the coordinate timelines to resolve precisely as &lt;strong&gt;December 21, 2022&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Executing &lt;code&gt;GetDays(2023, 25, 1)&lt;/code&gt; cleanly standardizes overflowing month structures to track as &lt;strong&gt;January 1, 2025&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;empirical-validation-bounds-and-architectural-integrity&quot;&gt;Empirical Validation Bounds and Architectural Integrity&lt;/h4&gt;
&lt;p&gt;To cross-examine the symmetrical precision of this mathematical matrix, a bidirectional subtraction verification loop was executed: &lt;code&gt;=ABS(Original_Year - Decoded_Year) + ...&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;governed-by-perfectly-balanced-fixed-point-scaling-properties-integer-divisors-365243-3653-and-306-the-execution-kernel-yields-an-absolute-zero-bit-error-rate-over-staggering-historical-epochs-the-verified-operational-envelope-spans-seamlessly-from-january-1-0001-through-april-30-487-621-&quot;&gt;Governed by perfectly balanced fixed-point scaling properties (integer divisors &lt;code&gt;365243&lt;/code&gt;, &lt;code&gt;3653&lt;/code&gt;, and &lt;code&gt;306&lt;/code&gt;), the execution kernel yields an absolute &lt;strong&gt;zero-bit error rate&lt;/strong&gt; over staggering historical epochs. The verified operational envelope spans seamlessly from &lt;strong&gt;January 1, 0001, through April 30, 487,621&lt;/strong&gt;.&lt;/h3&gt;
&lt;h3 id=&quot;-part-3-bare-metal-domination-the-strategy-of-the-32-bit-signed-long&quot;&gt;⚡ Part 3. Bare-Metal Domination: The Strategy of the 32-bit Signed Long&lt;/h3&gt;
&lt;p&gt;The entire calculation loop and mathematical constant matrix are intentionally designed to execute within a standard &lt;strong&gt;32-bit signed integer space (&lt;code&gt;Long&lt;/code&gt; / &lt;code&gt;int32_t&lt;/code&gt;)&lt;/strong&gt;. This unlocks mission-critical architectural advantages at the micro-architecture level:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Instruction Cycle Efficiency&lt;/strong&gt;: The operations compile into raw single-cycle integer arithmetic, running natively on basic 32-bit embedded microcontrollers (ARM Cortex-M, RISC-V cores).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L1 Cache Optimization and Bandwidth Reduction&lt;/strong&gt;: A standard &lt;code&gt;Long&lt;/code&gt; variable commands exactly 4 bytes of system memory, contrasting with the 8 bytes demanded by a &lt;code&gt;Double&lt;/code&gt;. When parsing multi-terabyte financial transaction streams (Big Data), the L1 Data Cache localization capacity is immediately doubled, entirely mitigating performance loss from &lt;em&gt;Cache Misses&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SIMD Vectorization Parallel Output&lt;/strong&gt;: A singular 512-bit wide CPU vector processing lane (AVX-512) can natively hold and process &lt;strong&gt;16 chronological calculations simultaneously&lt;/strong&gt;. Moving data within a 32-bit integer boundary inherently doubles parallel batch processing speeds over standard 64-bit data pipelines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Direct Silicon Synthesis (FPGA / ASIC)&lt;/strong&gt;: This engine can be directly synthesized into a network controller's hardware layer, completing multi-century financial time transformations at raw fiber-optic line rate.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-4-the-scientific-core-smoothing-and-resolving-the-actual-actual-timeline-constraints-msu-proceedings-&quot;&gt;Part 4. The Scientific Core: Smoothing and Resolving the Actual/Actual Timeline Constraints (MSU Proceedings)&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;The analytical frameworks detailed within Parts 4 and 5 represent the core mathematical breakthroughs of my research paper officially published in the Proceedings of the Lomonosov Moscow State University International Conference (eLIBRARY ID: 49514570).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To resolve the strict astronomical calendar standard (&lt;strong&gt;Actual/Actual&lt;/strong&gt;), we must acquire a perfectly continuous and differentiable time coordinate. The overarching barrier to this is the step-wise, jumping behavior of annual calendar lengths ($D_i$, fluctuating between 365 and 366 days). This fluctuations split the time coordinate into a non-linear, fragmented, and non-differentiable space at leap-year boundaries.&lt;/p&gt;
&lt;p&gt;Throughout my research, this computational barrier was shattered with extraordinary success: &lt;strong&gt;I accomplished the task of smoothing and differentiating the non-differentiable&lt;/strong&gt;. I derived and mathematically established a &lt;strong&gt;continuous real value time-in-years coordinate&lt;/strong&gt;, which maps fragmented chronological milestones into a smooth, perfectly differentiable continuum:&lt;/p&gt;
&lt;p&gt;\[ G_i = y_i + \frac{\Delta_i}{D_i} \]&lt;/p&gt;
&lt;p&gt;Where $y_i$ represents the calendar year of the i-th date, $\Delta_i$ defines the strict sequential day index inside that year, and $D_i$ reflects the absolute day length of that year. The values are decomposed using an analytical day distribution polynomial derived by me without using conditional &lt;code&gt;IF&lt;/code&gt; switches:&lt;/p&gt;
&lt;p&gt;\[ \Delta_i = d_i + \lfloor 30.56 m_i \rfloor - 30 - \lfloor 0.1 m_i + 0.7 \rfloor \cdot (367 - D_i) \]&lt;/p&gt;
&lt;p&gt;\[ D_i = 365 + \left(\lfloor \frac{y_i}{4} \rfloor - \lfloor \frac{y_i - 1}{4} \rfloor\right) - \left(\lfloor \frac{y_i}{100} \rfloor - \lfloor \frac{y_i - 1}{100} \rfloor\right) + \left(\lfloor \frac{y_i}{400} \rfloor - \lfloor \frac{y_i - 1}{400} \right) \]&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Critical Scientific Insight&lt;/strong&gt;: The absolute calculation of this fractional year value can be performed within pure rational numbers with zero rounding error, bypassing the need for the classical Euclidean algorithm entirely. Because 365 and 366 are mathematically coprime, the entire sequence maps flawlessly into a unified integer fraction sharing a constant common denominator of 365 ⋅ 366.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Deploying this continuous chronological vector space $G_i$ yields the mathematical infrastructure necessary to definitively solve the Internal Rate of Return (&lt;strong&gt;IRR&lt;/strong&gt;) equation under irregular transaction patterns. The central IRR equation is reshaped into a continuous, non-disrupted function:&lt;/p&gt;
&lt;p&gt;\[ f(\text{IRR}) = \text{ДП}_0 + \sum_{i=1}^{n} \frac{\text{ДП}_i}{\prod_{j=1}^{i} \left(1 + \frac{\text{IRR}}{100\%} \cdot (G_j - G_{j-1})\right)} = 0 \]&lt;/p&gt;
&lt;p&gt;Exploiting the mathematical smoothness achieved across the input timeline, I extract its &lt;strong&gt;exact analytical first derivative f'(IRR)&lt;/strong&gt;, completely bypassing the crude numerical step-approximations that break down on non-linear data boundaries:&lt;/p&gt;
&lt;p&gt;\[ f'(\text{IRR}) = -\sum_{i=1}^{n} \frac{\text{ДП}_i \cdot \sum_{k=1}^{i} \frac{G_k - G_{k-1}}{1 + \frac{\text{IRR}}{100\%} \cdot (G_k - G_{k-1})}}{\prod_{j=1}^{i} \left(1 + \frac{\text{IRR}}{100\%} \cdot (G_j - G_{j-1})\right)} \]&lt;/p&gt;
&lt;p&gt;This mathematical framework guarantees &lt;strong&gt;absolute convergence&lt;/strong&gt;. The Newton-Raphson loop resolves the exact root of the equation &lt;strong&gt;in exactly 3 iterations&lt;/strong&gt;, maintaining a precision scale down to one-millionth of a percent even when mapping highly volatile, non-periodic transaction schedules under strict operational constraints!&lt;/p&gt;
&lt;p&gt;Below is the production-grade programmatic implementation of this analytical model, written in standard VBA:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-vba&quot;&gt;Function AmIRR(CashFlows As Range, Dates As Range, Optional Guess As Double = 0) As Variant 
  If CashFlows.Count &amp;lt;&amp;gt; Dates.Count Then 
    AmIRR = &quot;#DIMENSIONS OF CASH FLOWS AND DATES DO NOT MATCH&quot; 
    Exit Function
  ElseIf CashFlows.Count = 1 Then 
    AmIRR = &quot;#INSUFFICIENT VALUES&quot; 
    Exit Function
  End If 

  Dim AmIRR0 As Double, f As Double, diff As Double, t As Double, df As Double
  Dim i As Long, j As Long, k As Long
  AmIRR = Guess 
  AmIRR0 = AmIRR + 0.00001 
  j = 1 
  
  Dim DatesInYears() As Double 
  ReDim DatesInYears(Dates.Count) 

  If Dates(1) &amp;gt; 366 Then 
    For i = 1 To Dates.Count 
      DatesInYears(i) = DateInYears(Dates(i)) 
    Next 
  Else 
    For i = 1 To Dates.Count 
      DatesInYears(i) = Dates(i) 
    Next 
  End If 

  While Abs(AmIRR0 - AmIRR) &amp;gt;= 0.000000000000001 And j &amp;lt; 100 
    f = CashFlows(1) 
    diff = 0 
    t = 1 
    For i = 2 To CashFlows.Count 
      t = t * (1 + AmIRR * (DatesInYears(i) - DatesInYears(i - 1))) 
      f = f + CashFlows(i) / t 
      df = 0 
      For k = 2 To i 
        df = df + (DatesInYears(k) - DatesInYears(k - 1)) / (1 + AmIRR * (DatesInYears(k) - DatesInYears(k - 1))) 
      Next 
      diff = diff - CashFlows(i) * df / t 
    Next 
    AmIRR0 = AmIRR AmIRR = AmIRR - f / diff j = j + 1 
  Wend 

  If j = 100 Then 
    AmIRR = &quot;#SERIES DID NOT CONVERGE:&quot; &amp;amp; AmIRR &amp;amp; &quot;;&quot; &amp;amp; AmIRR0 
  End If 
End Function 
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-part-5-conclusions&quot;&gt;️ Part 5. Conclusions&lt;/h3&gt;
&lt;p&gt;This phenomenal success within complex practical environments, validated by Lomonosov Moscow State University and archived via eLIBRARY, can and absolutely must be exported to global engineering practices. This shift is fundamentally dictated by the strict runtime demands of ultra-high-speed streaming data pipelines operating over massive, enterprise-scale real-time datasets (Big Data), where ensuring absolute mathematical correctness is the only mechanism to guarantee a total elimination of endless legal, regulatory, and corporate financial disputes.&lt;/p&gt;
&lt;p&gt;While it is currently premature to formally elaborate on the underlying algebraic intersections linking my newly discovered field of a,b-Romanions to this specific application, I am already standing on the precipice of an even more impactful paradigm shift that will comprehensively reconstruct contemporary perspectives on high-speed technologies of the future. Effectively, I am on the verge of formulating a universal, ultra-fast programming paradigm for the next generation of computing architecture. The established doctrine of dialectical materialism — stating that &quot;everything in the universe is interconnected and interdependent&quot; — has found its ultimate empirical realization right here within this body of work. This architecture, even when evaluated completely independent of the broader Romanion field, already stands as a comprehensive, independent super-technological breakthrough.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Романенко Р. В. Формула полной стоимости кредита // Инновационная экономика и менеджмент: методы и технологии : сборник статей участников VI Международной научно-практической конференции, Москва, 26–27 октября 2021 года. – Москва : Издательский Дом (типография) Московского государственного университета имени М. В. Ломоносова, 2021. – С. 238–244. – EDN: ZKMPWF. – eLIBRARY ID: 49514570. – URL: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.elibrary.ru/item.asp?id=49514570&quot;&gt;https://www.elibrary.ru/item.asp?id=49514570&lt;/a&gt; .&lt;/li&gt;
&lt;li&gt;Романенко Р. В., ПАВЛОВ В. А. Решение класса диофантовых уравнений вида: $x^2-y^2=z^n$ // Инновационные научные исследования. – 2021. – № 5-1 (7). – С. 6–14. – УДК: 511.52. – eISSN: 2713-0010. – EDN: EUQQTF. – eLIBRARY ID: 46584125. – DOI: 10.5281/zenodo.5007170. – URL: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.elibrary.ru/item.asp?id=46584125&quot;&gt;https://www.elibrary.ru/item.asp?id=46584125&lt;/a&gt;   .&lt;/li&gt;
&lt;/ol&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30680/the-magic-branchless-fintech-tame-nasd-financial-standard-solve-equation-without-single</guid>
<pubDate>Sun, 11 Oct 2026 04:34:52 +0000</pubDate>
</item>
<item>
<title>Elasticsearch with Ruby on Rails</title>
<link>https://coderlegion.com/30406/elasticsearch-with-ruby-on-rails</link>
<description>&lt;p&gt;🚀 &lt;strong&gt;Elasticsearch + Ruby on Rails: Beyond Database Queries&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ever noticed how searching millions of records can become slow and frustrating?&lt;/p&gt;
&lt;p&gt;When your application needs &lt;strong&gt;fast, advanced, and intelligent search&lt;/strong&gt;, Elasticsearch can make a real difference.&lt;/p&gt;
&lt;h4 id=&quot;-what-is-elasticsearch-&quot;&gt;🔍 What is Elasticsearch?&lt;/h4&gt;
&lt;p&gt;Elasticsearch is a distributed search and analytics engine built on Apache Lucene. It helps applications search, filter, and analyze large amounts of data efficiently.&lt;/p&gt;
&lt;p&gt;Unlike traditional database queries, it offers powerful full-text search, relevance scoring, and flexible filtering.&lt;/p&gt;
&lt;h4 id=&quot;-how-do-we-use-it-with-ruby-on-rails-&quot;&gt;🛠️ How do we use it with Ruby on Rails?&lt;/h4&gt;
&lt;p&gt;Imagine you're building an e-commerce platform with thousands of products.&lt;/p&gt;
&lt;p&gt;Users expect to search by product name, description, category, or even partial keywords — and get relevant results quickly.&lt;/p&gt;
&lt;p&gt;With Rails, you can integrate Elasticsearch using gems such as &lt;code&gt;elasticsearch-rails&lt;/code&gt; or &lt;code&gt;searchkick&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, using Searchkick:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;# Gemfile
gem &quot;searchkick&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;class Product &amp;lt; ApplicationRecord
  searchkick

  def search_data
    {
      name: name,
      description: description,
      category: category
    }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Search products with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;Product.search(&quot;wireless headphones&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of relying only on basic SQL matching, Elasticsearch can provide relevance-based results, typo tolerance, and advanced filtering.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: Elasticsearch must be installed and configured, and the data must be indexed before searching.&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;-why-use-elasticsearch-&quot;&gt;💡 Why use Elasticsearch?&lt;/h4&gt;
&lt;p&gt;✅ &lt;strong&gt;Faster Search&lt;/strong&gt; — Search large datasets efficiently.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Better Relevance&lt;/strong&gt; — Rank results based on how closely they match the query.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Typo Tolerance&lt;/strong&gt; — Help users find results even when they misspell a word.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Advanced Filtering&lt;/strong&gt; — Combine full-text search with categories, prices, and other filters.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Analytics &amp;amp; Aggregations&lt;/strong&gt; — Analyze search trends and summarize large datasets.&lt;/p&gt;
&lt;h4 id=&quot;-one-important-architectural-decision&quot;&gt;⚠️ One important architectural decision&lt;/h4&gt;
&lt;p&gt;Elasticsearch is not a replacement for your primary database.&lt;/p&gt;
&lt;p&gt;Use PostgreSQL or MySQL as your source of truth, and Elasticsearch as your search engine.&lt;/p&gt;
&lt;p&gt;When Rails data changes, keep the search index synchronized — often through background jobs with Sidekiq — and account for indexing delays and failed jobs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal isn't to replace SQL. It's to give your application a search experience that SQL alone may struggle to deliver at scale.&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30406/elasticsearch-with-ruby-on-rails</guid>
<pubDate>Fri, 09 Oct 2026 06:22:39 +0000</pubDate>
</item>
<item>
<title>We Promise Transparency. Not the &quot;Liquid Glass&quot; Kind.</title>
<link>https://coderlegion.com/29977/we-promise-transparency-not-the-liquid-glass-kind</link>
<description>&lt;p&gt;Let's get the joke out of the way first, because I can already see someone's fingers hovering over the comment box: no, when we talk about &quot;transparency&quot; here, we are not launching a translucent UI skin with rounded corners and a marketing deck about &quot;depth&quot; and &quot;light.&quot; Nobody's redesigning your icons to look like they're floating in a glass of water. Apple can keep that one. Transparency, as we mean it, isn't about making things look like glass. It's about making sure there's nothing opaque left to hide behind in the first place.&lt;/p&gt;
&lt;h3 id=&quot;transparency-is-the-rarest-feature-in-modern-software&quot;&gt;Transparency Is the Rarest Feature in Modern Software&lt;/h3&gt;
&lt;p&gt;Here's the uncomfortable observation that started all of this: almost nothing you run today will actually tell you what it's doing. You call a function, and underneath it, a dozen layers you never asked for quietly make decisions on your behalf — a runtime schedules something, an allocator decides where your data actually lives, a framework &quot;helpfully&quot; retries a failed call you didn't know could fail, a standard library wraps three other standard libraries you've never heard of. Most of the time that's a perfectly reasonable trade. Most software genuinely benefits from not making every engineer re-derive memory management from first principles on a Tuesday.&lt;/p&gt;
&lt;p&gt;But somewhere along the way, &quot;we abstracted this for your convenience&quot; quietly turned into &quot;we abstracted this so thoroughly that nobody on the team, including us, can tell you with certainty what your program actually does when it touches the kernel.&quot; That's not convenience anymore. That's a black box with a changelog. And an entire generation of developers has been trained to treat &quot;it works, don't ask why&quot; as an acceptable final answer, because asking why usually means descending through four more layers of someone else's unexamined assumptions.&lt;/p&gt;
&lt;p&gt;Developer transparency, the version we actually care about, is the opposite stance, stated plainly: no hidden magic, no invisible runtime quietly squatting on your CPU cycles like a tenant you never agreed to lease to, and no stack of &quot;convenience&quot; layers standing between your code and the hardware that's supposed to be running it. You should be able to point at any behaviour your program exhibits and trace it to a decision — yours, specifically — not to an abstraction that made the call for you somewhere you'll never see.&lt;/p&gt;
&lt;p&gt;This is where Gloria comes in, because it's the sharpest, most literal expression of that idea we've built so far.&lt;/p&gt;
&lt;h3 id=&quot;what-transparency-actually-looks-like-in-gloria&quot;&gt;What Transparency Actually Looks Like in Gloria&lt;/h3&gt;
&lt;p&gt;When you write something in Gloria, you know exactly what instruction goes to the CPU, exactly what memory gets mapped, and exactly which register is holding your data at any given moment. You are not down on one knee, hat in hand, begging a heavyweight framework or a sprawling C standard library to please do the thing for you. You own the execution, start to finish, with nobody else's fingerprints on it.&lt;/p&gt;
&lt;p&gt;From a Linux perspective, Gloria treats the kernel as its one and only direct interface. No linking against &lt;code&gt;glibc&lt;/code&gt;. No standard C startup routine quietly initializing half a universe of state before your &lt;code&gt;main&lt;/code&gt; function has even had the decency to run. Gloria generates self-contained, raw ELF64 binaries for AMD64, and that's genuinely all that's in there.&lt;/p&gt;
&lt;p&gt;When your code needs to open a socket, allocate memory, or spin up a bit of lightweight concurrency, it doesn't go politely knocking on some third-party wrapper function's door and waiting to be let in. It loads the syscall number straight into &lt;code&gt;RAX&lt;/code&gt;, lays out the arguments exactly per the System V AMD64 ABI, and hits the kernel directly with a &lt;code&gt;syscall&lt;/code&gt; instruction. No chaperone. No translation layer quietly editorializing your request on the way through. The resulting binaries stay under &lt;strong&gt;9KB&lt;/strong&gt;, because there is precisely zero runtime bloat riding along for the trip. Put that next to the multi-megabyte &quot;hello world&quot; some modern frameworks ship by default, and you start to get an honest sense of exactly how much weight we've actually cut.&lt;/p&gt;
&lt;h3 id=&quot;why-this-needed-to-exist-at-all&quot;&gt;Why This Needed to Exist at All&lt;/h3&gt;
&lt;p&gt;Here's the pitch, cynicism included at no extra charge: modern systems engineering has gone soft. Not incompetent — soft. Comfortable. Padded on every side with abstractions that exist specifically to protect you from having to understand what your own program is doing. Most software doesn't need to care which register holds a value. This project exists for the software that does, and for the engineers who'd rather know than be protected from knowing.&lt;/p&gt;
&lt;p&gt;We wanted something that gives you the raw control and speed of C, the type expressiveness and memory-safety thinking of Rust, and the dead-simple concurrency model of Go — minus the overhead each of those languages still quietly carries around. This is built for the specific kind of developer who needs to wring every last drop of performance out of a box, who's writing ultra-fast microservices, custom network infrastructure, or logic that has to survive on genuinely minimal resources, where every kilobyte and every CPU cycle is a line item somebody's actually tracking, not an abstraction nobody bothers to measure.&lt;/p&gt;
&lt;h3 id=&quot;the-roadmap-stated-without-the-usual-hedging&quot;&gt;The Roadmap, Stated Without the Usual Hedging&lt;/h3&gt;
&lt;p&gt;Our self-hosting plan is straightforward and, I'll admit, a little uncompromising. Right now, the ForgeZero compiler, &lt;code&gt;fz&lt;/code&gt;, is written in Go. That's a deliberate choice, not a compromise we're quietly hoping nobody notices — Go let us bootstrap the language fast, iterate on syntax without a chicken-and-egg problem slowing every decision down, and shake the remaining edge cases out of the lexer, parser, and semantic checker while the ground was still allowed to move under our feet.&lt;/p&gt;
&lt;p&gt;But Go is a bridge, not a destination. The actual milestone the entire Gloria ecosystem is being built toward is full self-hosting. We're building out the language constructs, inline assembly support, and memory primitives specifically so that Gloria can eventually compile its own compiler, from scratch, with nothing borrowed. The day &lt;code&gt;fz&lt;/code&gt; is rewritten in pure Gloria is the day we cut the last external dependency loose entirely — and the day this architecture stops being &quot;an interesting idea with training wheels&quot; and starts being something you can point at and say, flatly, that it's ready for serious production.&lt;/p&gt;
&lt;p&gt;That's the whole philosophy, really, scaled up from one register to an entire compiler: nothing in this stack should exist that we can't fully explain, and nothing should run that isn't accountable to someone who actually understands it.&lt;/p&gt;
&lt;h3 id=&quot;watch-it-happen-instead-of-reading-about-it-later&quot;&gt;Watch It Happen Instead of Reading About It Later&lt;/h3&gt;
&lt;p&gt;If any of this is the kind of problem that genuinely interests you — not the polished retrospective six months from now, but the actual process, bugs included — we post it all in close to real time on our Telegram channel, &lt;strong&gt;@forgezeroteam&lt;/strong&gt;. Build updates, broken things, fixed things, syntax decisions argued out loud before they're final. It's faster and considerably more honest than waiting for the cleaned-up version to show up here.&lt;/p&gt;
&lt;p&gt;No liquid glass. No rounded corners pretending to be depth. Just a binary you can actually read, a register you can actually name, and a compiler that, one day soon, won't owe its existence to anything but itself.&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/29977/we-promise-transparency-not-the-liquid-glass-kind</guid>
<pubDate>Tue, 06 Oct 2026 11:10:46 +0000</pubDate>
</item>
<item>
<title>placeholder=&quot;blur&quot; on a Next.js Image Silently Does Nothing If You Forget One Thing</title>
<link>https://coderlegion.com/29729/placeholder-blur-on-a-next-js-image-silently-does-nothing-if-you-forget-one-thing</link>
<description>&lt;p&gt;&lt;code&gt;placeholder=&quot;blur&quot;&lt;/code&gt; is one of those props that works perfectly the first time you try it, in a quick example with a locally imported image, and then quietly stops doing anything the moment you swap that example for a real image coming from a CMS, S3, or any other remote source.&lt;/p&gt;
&lt;h3 id=&quot;the-example-that-works-and-why-it-works&quot;&gt;The Example That Works, and Why It Works&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import heroImage from '@/public/hero.jpg'; // a static, local import

&amp;lt;Image src={heroImage} alt=&quot;Hero&quot; placeholder=&quot;blur&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works because a statically imported image is processed at build time, and Next.js automatically generates a tiny, blurred base64 placeholder from the actual image's real content during that build step. You get a genuine, accurate blur preview with zero extra configuration, because the framework has direct, build-time access to the actual image file.&lt;/p&gt;
&lt;h3 id=&quot;where-it-silently-stops-working&quot;&gt;Where It Silently Stops Working&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// A product image coming from your CMS or a database field
&amp;lt;Image src={product.imageUrl} alt={product.name} placeholder=&quot;blur&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;product.imageUrl&lt;/code&gt; is a remote URL, a string, not a statically imported file. Next.js has no way to generate a blur placeholder from a URL at build time, since the actual image content isn't available until runtime, when the page is actually being requested, not when the project is built. Without a manually provided &lt;code&gt;blurDataURL&lt;/code&gt; prop, &lt;code&gt;placeholder=&quot;blur&quot;&lt;/code&gt; on a remote image source simply has nothing to use, and the image renders without a placeholder at all, exactly as if the prop had never been added.&lt;/p&gt;
&lt;h3 id=&quot;why-this-doesn-t-throw-an-error&quot;&gt;Why This Doesn't Throw an Error&lt;/h3&gt;
&lt;p&gt;This is genuinely a silent no-op, not a crash, which is exactly what makes it easy to miss. The prop exists, the code runs, the page renders, the image loads, just without the blur transition anyone added &lt;code&gt;placeholder=&quot;blur&quot;&lt;/code&gt; specifically to get. Nothing in the console points at the actual gap, since from Next.js's perspective, nothing is actually wrong, you simply didn't provide the one piece of data the feature needs for a remote source.&lt;/p&gt;
&lt;h3 id=&quot;the-fix-provide-blurdataurl-explicitly-for-remote-images&quot;&gt;The Fix: Provide blurDataURL Explicitly for Remote Images&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;Image
  src={product.imageUrl}
  alt={product.name}
  placeholder=&quot;blur&quot;
  blurDataURL=&quot;data:image/jpeg;base64,/9j/4AAQSkZJRg...&quot; // a real, small base64 placeholder
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this to genuinely work, you need an actual base64-encoded placeholder, generated from the real image, not a generic, unrelated blur applied to every image regardless of its actual content. A common approach is generating and storing this placeholder at the time an image is uploaded, saving it alongside the image URL itself in your database, so it's already available whenever the image needs to render, without a separate runtime computation on every single page load.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// A simplified example of generating a blur placeholder at upload time
import { getPlaiceholder } from 'plaiceholder';

async function generateBlurPlaceholder(imageUrl: string) {
  const buffer = await fetch(imageUrl).then((res) =&amp;gt; res.arrayBuffer());
  const { base64 } = await getPlaiceholder(Buffer.from(buffer));
  return base64; // store this alongside the image URL in your database
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Libraries like &lt;code&gt;plaiceholder&lt;/code&gt; exist specifically for this, generating a real, accurate blur placeholder from any image buffer, which you can then store once and reuse on every subsequent render, rather than regenerating it on the fly.&lt;/p&gt;
&lt;h3 id=&quot;a-cheaper-fallback-when-a-real-placeholder-isn-t-worth-the-effort&quot;&gt;A Cheaper Fallback When a Real Placeholder Isn't Worth the Effort&lt;/h3&gt;
&lt;p&gt;For cases where generating and storing a genuine per-image placeholder is more infrastructure than the project needs, a generic, static color placeholder is still better than silently getting nothing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;Image
  src={product.imageUrl}
  alt={product.name}
  placeholder=&quot;blur&quot;
  blurDataURL=&quot;data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxIiBoZWlnaHQ9IjEiPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjEiIGZpbGw9IiNlNWU3ZWIiLz48L3N2Zz4=&quot;
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a tiny, solid-color SVG, decoded, just a single gray rectangle, which still gives a smoother loading transition than nothing, even though it isn't a real, content-matched blur of the actual image. Not as polished as a genuine per-image placeholder, genuinely better than the silent no-op most remote images end up with by default.&lt;/p&gt;
&lt;h3 id=&quot;how-to-check-if-this-is-happening-on-your-own-site&quot;&gt;How to Check if This Is Happening on Your Own Site&lt;/h3&gt;
&lt;p&gt;Search for &lt;code&gt;placeholder=&quot;blur&quot;&lt;/code&gt; across your codebase and check, for each match, whether the &lt;code&gt;src&lt;/code&gt; is a static import or a remote URL. Any remote &lt;code&gt;src&lt;/code&gt; without an accompanying &lt;code&gt;blurDataURL&lt;/code&gt; prop is this exact gap, the prop is present, and doing nothing.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep -B2 -A2 'placeholder=&quot;blur&quot;' --include=&quot;*.tsx&quot; -r .
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-actual-rule&quot;&gt;The Actual Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;placeholder=&quot;blur&quot;&lt;/code&gt; only works automatically for statically imported images. Any remote image source needs an explicit &lt;code&gt;blurDataURL&lt;/code&gt;, generated from the real image content, or the prop silently does nothing rather than failing loudly.&lt;/strong&gt; Generating and storing that placeholder once, at upload time, is the more complete fix. A generic, static color placeholder is a reasonable, low-effort fallback when a real one isn't worth the infrastructure for a given project.&lt;/p&gt;
&lt;p&gt;I handle this exact pattern, generating and storing real blur placeholders at upload time, across the dashboards and templates I build at &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pixelanas.com&quot;&gt;pixelanas.com&lt;/a&gt;, and I've covered related image and performance patterns on the &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pixelanas.com/blog&quot;&gt;blog&lt;/a&gt; too.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you're using &lt;code&gt;placeholder=&quot;blur&quot;&lt;/code&gt; anywhere with a remote image source, check whether &lt;code&gt;blurDataURL&lt;/code&gt; is actually present. If it's not, the prop has been doing nothing this whole time. Drop what you find in the comments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29729/placeholder-blur-on-a-next-js-image-silently-does-nothing-if-you-forget-one-thing</guid>
<pubDate>Sun, 04 Oct 2026 13:33:12 +0000</pubDate>
</item>
<item>
<title>Benchmark Android User Journeys with Macrobenchmark</title>
<link>https://coderlegion.com/29723/benchmark-android-user-journeys-with-macrobenchmark</link>
<description>&lt;p&gt;Unit tests can prove that a ViewModel returns the right state, but they cannot measure the latency and frame behavior a user experiences across a sequence of screens and a real application process. To understand real-world responsiveness, you need to use Macrobenchmark with UI Automator to drive a complete interaction from process launch to the user-visible refreshed state, then analyze frame and milestone timings.&lt;/p&gt;
&lt;h3 id=&quot;understanding-macrobenchmark-scope&quot;&gt;Understanding Macrobenchmark Scope&lt;/h3&gt;
&lt;p&gt;The useful distinction in performance testing is scope. A unit test isolates a method or state transition in memory. A standard UI test checks user-visible behavior and assertions against functional correctness. A Macrobenchmark runs outside the target app process and measures a larger end-user interaction with controlled compilation and performance metrics.&lt;/p&gt;
&lt;p&gt;Measuring a complete journey offers visibility into performance issues that isolated tests miss. For example, a multi-screen order-entry flow involves several distinct phases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Launching the application and waiting for the home screen.&lt;/li&gt;
&lt;li&gt;Opening an order-entry surface.&lt;/li&gt;
&lt;li&gt;Selecting a package and filling required fields.&lt;/li&gt;
&lt;li&gt;Submitting the form.&lt;/li&gt;
&lt;li&gt;Waiting for the success message.&lt;/li&gt;
&lt;li&gt;Waiting until the new item appears in the pending list.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Measuring these steps together reveals delays that occur after an API response, such as rendering a refreshed list or handling complex layout inflation during navigation. For a related implementation, see &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/managing-concurrent-git-commits-during-automated-publishing/&quot;&gt;Managing Concurrent Git Commits During Automated&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;configuring-the-benchmark-target&quot;&gt;Configuring the Benchmark Target&lt;/h3&gt;
&lt;p&gt;To run a reliable macrobenchmark, your application must be configured as profileable rather than debuggable. A debuggable build includes performance overhead from the Java Virtual Machine debugger and JIT constraints that distort real-world timing. A profileable build disables debugging while still permitting the benchmarking tool to capture trace information and metric counters.&lt;/p&gt;
&lt;p&gt;In your app-level build file, you configure the profileable manifest property inside the release or benchmark build type. The snippet below shows how to configure a dedicated benchmark build variant:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-groovy&quot;&gt;android {
    buildTypes {
        create(&quot;benchmark&quot;) {
            initWith(buildTypes.getByName(&quot;release&quot;))
            signingConfig = signingConfigs.getByName(&quot;debug&quot;)
            matchingFallbacks += listOf(&quot;release&quot;)
            applicationIdSuffix = &quot;.benchmark&quot;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alongside the build configuration, the instrumentation test must drive the application using UI Automator selectors. The test runner launches the process, executes the touch and input actions, and measures the resulting frame metrics without altering app bytecode.&lt;/p&gt;
&lt;h3 id=&quot;driving-the-flow-with-ui-automator&quot;&gt;Driving the Flow with UI Automator&lt;/h3&gt;
&lt;p&gt;The benchmark test structure coordinates between the host test process and the target application process. Below is an example of a Macrobenchmark test class that launches an order-entry journey and measures the transition:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@RunWith(AndroidJUnit4::class)
@LargeTest
public class OrderJourneyBenchmark {

    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    public fun benchmarkOrderSubmission() {
        benchmarkRule.measureRepeated(
            packageName = &quot;com.example.app.benchmark&quot;,
            metrics = listOf(FrameTimingMetric(), StartupTimingMetric()),
            compilationMode = CompilationMode.DEFAULT,
            iterations = 5
        ) {
            pressHome()
            startActivityAndWait()

            // Open order entry surface
            device.findObject(By.res(&quot;com.example.app&quot;, &quot;button_new_order&quot;))
                .click()
            device.wait(Until.hasObject(By.res(&quot;com.example.app&quot;, &quot;form_container&quot;)), 3000)

            // Fill fields and submit
            device.findObject(By.res(&quot;com.example.app&quot;, &quot;input_package_name&quot;))
                .text = &quot;Standard Package&quot;
            device.findObject(By.res(&quot;com.example.app&quot;, &quot;button_submit&quot;))
                .click()

            // Wait for success message and refreshed list
            device.wait(Until.hasObject(By.res(
                &quot;com.example.app&quot;, 
                &quot;text_success_banner&quot;
            )), 5000)
            device.wait(Until.hasObject(By.res(
                &quot;com.example.app&quot;, 
                &quot;list_pending_orders&quot;
            )), 5000)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;managing-side-effects-and-data-isolation&quot;&gt;Managing Side Effects and Data Isolation&lt;/h3&gt;
&lt;p&gt;Running realistic user flows introduces operational side effects that require careful management. A flow that submits forms or creates records will write durable data to your backend or local database. If left unmanaged, repeated benchmark runs accumulate test artifacts that bloat local storage or clutter backend environments.&lt;/p&gt;
&lt;p&gt;To maintain valid performance numbers, you should isolate test data using a dedicated staging environment, implement automated database cleanup routines after each iteration, or use disposable test fixtures. Never point an automated performance benchmark against a production database without strict isolation controls. For a related implementation, see &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/safe-multi-environment-database-orchestration/&quot;&gt;Safe Multi Environment Database Orchestration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Another common pitfall is relying on insufficient sample sizes. A single measured iteration does not yield a reliable distribution or regression baseline. Performance numbers vary due to thermal throttling, background system processes, and CPU frequency scaling. Increasing the iteration count and observing the median value helps filter out transient noise.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Measuring complete user journeys with Macrobenchmark bridges the gap between synthetic unit tests and real-world user experience. By combining profileable builds, UI Automator drivers, and proper data isolation, you can establish repeatable performance baselines that capture multi-screen rendering behavior and latency across your entire application flow.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29723/benchmark-android-user-journeys-with-macrobenchmark</guid>
<pubDate>Sun, 04 Oct 2026 12:02:47 +0000</pubDate>
</item>
<item>
<title>WebsiteAudit.com</title>
<link>https://coderlegion.com/29612/websiteaudit-com</link>
<description>&lt;p&gt;WebsiteAudit.com shows what is broken, what is slow, and what is hurting search rankings. In about 3 minutes it runs 75+ checks across 15 categories, including performance, SEO, security, and accessibility. Each issue has a plain-language explanation and specific fix steps, ranked by impact.&lt;/p&gt;
&lt;p&gt;Pricing: Freemium. The first audit is free. Then $4.99 for 1, $21.21 for 5, and $39.92 for 10.&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/29612/websiteaudit-com</guid>
<pubDate>Sat, 03 Oct 2026 21:27:50 +0000</pubDate>
</item>
<item>
<title>Your Server Component Might Be Fetching Data Three Times Slower Than It Needs To</title>
<link>https://coderlegion.com/29565/your-server-component-might-be-fetching-data-three-times-slower-than-it-needs-to</link>
<description>&lt;p&gt;This is one of the easiest performance mistakes to write without noticing, since the code reads completely naturally, top to bottom, each line waiting for the one before it to finish, exactly the way most people learn to write async code in the first place.&lt;/p&gt;
&lt;h3 id=&quot;the-setup-that-reads-perfectly-naturally&quot;&gt;The Setup That Reads Perfectly Naturally&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/dashboard/page.tsx
export default async function DashboardPage() {
  const user = await getUser();
  const posts = await getPosts();
  const comments = await getComments();
  const analytics = await getAnalytics();

  return (
    &amp;lt;Dashboard
      user={user}
      posts={posts}
      comments={comments}
      analytics={analytics}
    /&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four &lt;code&gt;await&lt;/code&gt; calls, one after another. Clean, readable, and exactly how sequential code naturally gets written when each line is just &quot;get the next thing I need.&quot; The problem is that none of these four calls actually depend on the results of the others, &lt;code&gt;getPosts&lt;/code&gt; doesn't need anything from &lt;code&gt;getUser&lt;/code&gt;, &lt;code&gt;getComments&lt;/code&gt; doesn't need anything from &lt;code&gt;getPosts&lt;/code&gt;, and so on. They're all independently fetchable at the same time, and writing them sequentially means the total page load time is the sum of all four, not the time of the single slowest one.&lt;/p&gt;
&lt;h3 id=&quot;why-this-costs-more-than-it-looks-like-it-should&quot;&gt;Why This Costs More Than It Looks Like It Should&lt;/h3&gt;
&lt;p&gt;If each of these four calls takes roughly 150 milliseconds, a genuinely reasonable time for a real database query, the sequential version takes roughly 600 milliseconds total, each one waiting for the previous one to fully complete before even starting. Run the same four calls concurrently instead, and the total time is roughly 150 milliseconds, the time of the single slowest call, since they're all genuinely happening at once rather than queued up one behind another for no actual reason.&lt;/p&gt;
&lt;p&gt;This is a real, measurable difference on a page real users are waiting for, and it's entirely avoidable, since nothing about these four pieces of data actually requires this sequential order, the code just happens to be written in a shape that imposes one anyway.&lt;/p&gt;
&lt;h3 id=&quot;the-actual-fix-promise-all-for-independent-fetches&quot;&gt;The Actual Fix: Promise.all for Independent Fetches&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default async function DashboardPage() {
  const [user, posts, comments, analytics] = await Promise.all([
    getUser(),
    getPosts(),
    getComments(),
    getAnalytics(),
  ]);

  return (
    &amp;lt;Dashboard
      user={user}
      posts={posts}
      comments={comments}
      analytics={analytics}
    /&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Promise.all&lt;/code&gt; starts all four requests essentially simultaneously and waits for all of them to complete together, rather than starting each one only after the last one finished. Same data, same final render, a fraction of the total wait, purely by removing an ordering constraint that was never actually necessary in the first place.&lt;/p&gt;
&lt;h3 id=&quot;where-this-gets-genuinely-trickier-partial-dependencies&quot;&gt;Where This Gets Genuinely Trickier: Partial Dependencies&lt;/h3&gt;
&lt;p&gt;Not every case is this clean. Sometimes one piece of data genuinely depends on another, and mixing dependent and independent fetches needs a bit more care than a single flat &lt;code&gt;Promise.all&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// ❌ Everything sequential, even though posts and analytics don't need each other
export default async function DashboardPage() {
  const user = await getUser();
  const posts = await getPostsForUser(user.id); // genuinely depends on user
  const analytics = await getAnalytics(); // does NOT depend on user at all
  return &amp;lt;Dashboard user={user} posts={posts} analytics={analytics} /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// ✅ Only the genuine dependency stays sequential, everything else runs alongside it
export default async function DashboardPage() {
  const analyticsPromise = getAnalytics(); // starts immediately, not awaited yet

  const user = await getUser();
  const posts = await getPostsForUser(user.id); // genuinely needs user.id first

  const analytics = await analyticsPromise; // was already running this whole time

  return &amp;lt;Dashboard user={user} posts={posts} analytics={analytics} /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Starting &lt;code&gt;getAnalytics()&lt;/code&gt; without immediately awaiting it lets that request begin running in the background while &lt;code&gt;getUser&lt;/code&gt; and &lt;code&gt;getPostsForUser&lt;/code&gt; proceed through their genuine, necessary sequence. By the time the code actually needs the analytics result, it's very likely already finished, or at least has been running the whole time instead of only starting after everything else completed.&lt;/p&gt;
&lt;h3 id=&quot;how-to-actually-spot-this-in-your-own-code&quot;&gt;How to Actually Spot This in Your Own Code&lt;/h3&gt;
&lt;p&gt;Look for multiple &lt;code&gt;await&lt;/code&gt; statements in a row where each one calls a genuinely independent function, nothing on the left side of an earlier line appears as an argument to a later one. That absence of a real data dependency is the signal, these calls have no actual reason to be sequential, and wrapping them in &lt;code&gt;Promise.all&lt;/code&gt; is very likely a free, meaningful performance improvement with zero behavior change.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;grep -B2 -A2 &quot;await get&quot; app/**/page.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Scanning for clusters of consecutive &lt;code&gt;await&lt;/code&gt; calls to different functions, then checking whether any of them genuinely reference the previous line's result, is a quick, practical way to find real waterfall candidates across an existing codebase.&lt;/p&gt;
&lt;h3 id=&quot;why-this-matters-more-on-pages-with-several-independent-data-sources&quot;&gt;Why This Matters More on Pages With Several Independent Data Sources&lt;/h3&gt;
&lt;p&gt;A dashboard, a page with several distinct widgets or sections each backed by their own query, is exactly where this pattern accumulates the most cost, since it's common to have four, five, or more genuinely independent pieces of data feeding one page, each one innocently written as its own &lt;code&gt;await&lt;/code&gt; line, each one adding its full latency on top of all the others instead of overlapping.&lt;/p&gt;
&lt;h3 id=&quot;the-actual-rule-1&quot;&gt;The Actual Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Before awaiting multiple async calls sequentially, check whether any of them genuinely need a previous call's result. If none do, &lt;code&gt;Promise.all&lt;/code&gt; turns their combined wait time from a sum into a maximum, for free, with no behavior change.&lt;/strong&gt; For a genuine mix of dependent and independent calls, start the independent ones early without immediately awaiting them, so they run in the background while the real, necessary sequence proceeds.&lt;/p&gt;
&lt;p&gt;I check every Server Component I write for exactly this pattern now, across client work and the dashboards and templates I build at &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pixelanas.com&quot;&gt;pixelanas.com&lt;/a&gt;, since it's one of the highest-value, lowest-effort performance fixes available, and I've written about related data-fetching patterns on the &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pixelanas.com/blog&quot;&gt;blog&lt;/a&gt; too.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Go check your own dashboard or any data-heavy page for consecutive &lt;code&gt;await&lt;/code&gt; calls with no genuine dependency between them. If you find some, wrapping them in &lt;code&gt;Promise.all&lt;/code&gt; is very likely a free win sitting right there. Drop what you find in the comments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29565/your-server-component-might-be-fetching-data-three-times-slower-than-it-needs-to</guid>
<pubDate>Fri, 02 Oct 2026 18:05:04 +0000</pubDate>
</item>
<item>
<title>Stop Overworking Your Server: A Developer’s Guide to HTTP Caching</title>
<link>https://coderlegion.com/29021/stop-overworking-your-server-a-developers-guide-to-http-caching</link>
<description>&lt;p&gt;HTTP caching headers are instructions sent between a web server and a browser that dictate whether a file (like an image, script, or stylesheet) should be saved locally by the user or requested again from the server. By managing these instructions, developers can prevent unnecessary data transfers and ensure the browser only fetches content when absolutely required.&lt;/p&gt;
&lt;h4 id=&quot;the-library-analogy&quot;&gt;The Library Analogy&lt;/h4&gt;
&lt;p&gt;Imagine you are a researcher visiting a massive national library. The first time you need a book, you wait in line for the librarian to retrieve it from the archives. This takes time. Now, imagine the librarian gives you a sticky note on the cover saying, &quot;Keep this on your personal desk for three days.&quot; Instead of walking back to the archives every time you need that information, you simply look at your desk. The library is less crowded, you work faster, and the librarian has more time to help others. HTTP caching headers are those sticky notes; they tell the browser how long to &quot;keep the book on the desk&quot; before it must go back to the library server to check for a newer version.&lt;/p&gt;
&lt;h4 id=&quot;why-it-matters&quot;&gt;Why It Matters&lt;/h4&gt;
&lt;p&gt;In the tech industry, &quot;performance&quot; is often synonymous with &quot;user retention.&quot; Every time a browser asks for a file, it consumes server bandwidth and adds latency—the delay between a user's request and the site's response. Engineers use caching headers to minimize &quot;round-trips&quot; to the server. Without them, a user’s browser would re-download your entire JavaScript bundle and all your CSS files on every single page load, even if the files haven't changed. This causes slow page loads and unnecessarily high cloud infrastructure costs.&lt;/p&gt;
&lt;h4 id=&quot;implementation-in-express-js&quot;&gt;Implementation in Express.js&lt;/h4&gt;
&lt;p&gt;As a Node.js developer, you can control this behavior easily within your Express.js routes. Here is how you might set a &quot;Cache-Control&quot; header to tell the browser to keep a static file for one hour:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const express = require('express');
const app = express();

app.get('/api/data', (req, res) =&amp;gt; {
  // max-age is defined in seconds (3600 seconds = 1 hour)
  res.set('Cache-Control', 'public, max-age=3600');
  res.json({ message: 'This data will be cached by the browser for an hour' });
});

app.listen(3000);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-takeaway&quot;&gt;The Takeaway&lt;/h4&gt;
&lt;p&gt;Caching is not just about speed; it is about intelligence. By properly configuring headers, you shift the workload from your server to the user's browser, resulting in a more resilient application that saves resources while providing a near-instant experience for returning visitors.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/09/mastering-performance-secret-power-of.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29021/stop-overworking-your-server-a-developers-guide-to-http-caching</guid>
<pubDate>Tue, 29 Sep 2026 10:10:51 +0000</pubDate>
</item>
<item>
<title>API Performance Profiler: Live route latency directly inside VS Code ⚡</title>
<link>https://coderlegion.com/28793/api-performance-profiler-live-route-latency-directly-inside-vs-code</link>
<description>&lt;p&gt;Hey everyone! 👋 I'm Nahid, the creator of API Performance Profiler.&lt;br&gt;
As backend developers, we often fly blind when building APIs locally. You write a route, but you don't really know how fast it runs until you leave your editor, set up Postman, or configure a heavy APM tool. &lt;br&gt;
I wanted performance testing to feel native and frictionless, so I built an open-source, zero-dependency local profiling extension for Node.js (Express &amp;amp; NestJS). &lt;br&gt;
Here is what it brings directly to your editor:&lt;br&gt;
🟢 Inline Latency Metrics: See real-time execution speeds (e.g., 🟢 84ms) rendered right beside your route definitions in the code.&lt;br&gt;
🚀 1-Click Load Testing: A '▶ Load test' CodeLens sits directly above your route. Click it to replay the last recorded request 1000 times under heavy load.&lt;br&gt;
📊 Routes Dashboard: A dedicated VS Code sidebar showing all observed routes in your app, sorted from slowest to fastest.&lt;br&gt;
🔐 100% Local &amp;amp; Secure: Everything is stored in local memory. No cloud accounts, no external dashboards. &lt;br&gt;
It takes less than a minute to set up. I’d love for you to try it out!&lt;br&gt;
🔗 VS Code Marketplace: &lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=nahid625.api-profiler-vscode&quot;&gt;https://marketplace.visualstudio.com/items?itemName=nahid625.api-profiler-vscode&lt;/a&gt;&lt;br&gt;
🐙 GitHub Repo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Nahid625/api-performance-profiler&quot;&gt;https://github.com/Nahid625/api-performance-profiler&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28793/api-performance-profiler-live-route-latency-directly-inside-vs-code</guid>
<pubDate>Sun, 27 Sep 2026 10:25:03 +0000</pubDate>
</item>
<item>
<title>JSON vs MessagePack in Laravel: A Practical API Benchmark</title>
<link>https://coderlegion.com/28486/json-vs-messagepack-in-laravel-a-practical-api-benchmark</link>
<description>&lt;p&gt;JSON is the right default for most Laravel APIs. It is readable, supported everywhere, easy to debug, and familiar to every client.&lt;/p&gt;
&lt;p&gt;MessagePack is worth considering when payload size, bandwidth, or a controlled client ecosystem matters. It is a compact binary format that keeps a schema-free data model, but it is not automatically faster or smaller for every workload.&lt;/p&gt;
&lt;p&gt;This article compares both formats in a Laravel API and shows how to adopt MessagePack without breaking existing JSON clients.&lt;/p&gt;
&lt;p&gt;The examples use &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/smmehdisharifi/laravel-msgpack&quot;&gt;Laravel Msgpack&lt;/a&gt;, an open-source package that adds opt-in MessagePack content negotiation, request decoding, safety limits, and a repeatable benchmark command.&lt;/p&gt;
&lt;h3 id=&quot;the-short-answer&quot;&gt;The short answer&lt;/h3&gt;
&lt;p&gt;Use JSON when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your clients include browsers, third-party integrations, or unknown consumers.&lt;/li&gt;
&lt;li&gt;Human-readable responses and simple debugging are important.&lt;/li&gt;
&lt;li&gt;Your payloads are already small or compressed effectively.&lt;/li&gt;
&lt;li&gt;You do not control the client decoder and deployment environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use MessagePack when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You control the clients and can ship a binary decoder.&lt;/li&gt;
&lt;li&gt;Network transfer size is important for mobile, edge, or internal services.&lt;/li&gt;
&lt;li&gt;Your payloads are large enough for serialization and decoding trade-offs to matter.&lt;/li&gt;
&lt;li&gt;You want to keep JSON compatibility while allowing capable clients to opt in.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For many Laravel applications, the best answer is not choosing one format globally. Keep JSON as the default and negotiate MessagePack per request.&lt;/p&gt;
&lt;h3 id=&quot;what-are-we-comparing-&quot;&gt;What are we comparing?&lt;/h3&gt;
&lt;p&gt;JSON is a text representation. It is self-describing, easy to inspect, and supported by the web platform.&lt;/p&gt;
&lt;p&gt;MessagePack is a binary representation. It encodes the same basic data model using compact type markers and lengths instead of textual punctuation and names repeated in a text format.&lt;/p&gt;
&lt;p&gt;The comparison should use the same data, the same PHP runtime, and the same operation count. Otherwise, the result is a benchmark of different workloads rather than a comparison of formats.&lt;/p&gt;
&lt;h3 id=&quot;add-messagepack-to-laravel&quot;&gt;Add MessagePack to Laravel&lt;/h3&gt;
&lt;p&gt;Install the package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;composer require smmehdisharifi/laravel-msgpack
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apply the middleware to a route or route group:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;use Illuminate\Support\Facades\Route;

Route::middleware('msgpack')-&amp;gt;get('/api/profile', function () {
    return [
        'id' =&amp;gt; 1042,
        'name' =&amp;gt; 'Laravel MessagePack',
        'roles' =&amp;gt; ['maintainer', 'developer'],
        'features' =&amp;gt; [
            'negotiation' =&amp;gt; true,
            'fallback' =&amp;gt; 'json',
        ],
    ];
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The route does not need separate JSON and MessagePack controllers. Existing clients continue to receive JSON:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -i http://localhost/api/profile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A capable client opts into MessagePack with the Accept header. Save the binary response instead of printing it as terminal text:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -sS -D - \
  -H 'Accept: application/msgpack' \
  -o profile.msgpack \
  http://localhost/api/profile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The negotiated response includes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;Content-Type: application/msgpack
Vary: Accept
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vary: Accept is important because a cache must not serve a MessagePack response to a client that asked for JSON, or the other way around.&lt;/p&gt;
&lt;h3 id=&quot;how-content-negotiation-works&quot;&gt;How content negotiation works&lt;/h3&gt;
&lt;p&gt;The middleware keeps the migration opt-in:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A client that does not request MessagePack receives JSON.&lt;/li&gt;
&lt;li&gt;A client that sends Accept: application/msgpack receives a binary response when the route supports the middleware.&lt;/li&gt;
&lt;li&gt;If both formats are accepted, the higher quality factor wins.&lt;/li&gt;
&lt;li&gt;Wildcards are evaluated by media-type specificity.&lt;/li&gt;
&lt;li&gt;A request that rejects every supported representation receives 406 Not Acceptable.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The same negotiation applies to relevant error responses, including unmatched routes and route exceptions. Incoming requests can use application/msgpack or the legacy application/x-msgpack media type.&lt;/p&gt;
&lt;h3 id=&quot;run-a-repeatable-benchmark&quot;&gt;Run a repeatable benchmark&lt;/h3&gt;
&lt;p&gt;Laravel Msgpack includes an Artisan command so the comparison is easy to reproduce:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;php artisan msgpack:benchmark --iterations=1000 --json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command uses a deterministic API-shaped fixture with nested values and Unicode text. It measures:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raw JSON and MessagePack byte size.&lt;/li&gt;
&lt;li&gt;Encode time per iteration.&lt;/li&gt;
&lt;li&gt;Decode time per iteration.&lt;/li&gt;
&lt;li&gt;Optional gzip size and compression timings when ext-zlib is available.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The command warms each operation once before timing it. It reports per-iteration timings so runs with different iteration counts remain comparable.&lt;/p&gt;
&lt;h3 id=&quot;results-from-this-package&quot;&gt;Results from this package&lt;/h3&gt;
&lt;p&gt;The following result was produced by the package benchmark on PHP 8.4 with rybakit/msgpack 0.7.2 and 1,000 iterations:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Measurement &lt;/th&gt;&lt;th&gt;JSON &lt;/th&gt;&lt;th&gt;MessagePack &lt;/th&gt;&lt;th&gt;Difference&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Raw payload &lt;/td&gt;&lt;td&gt;591 B &lt;/td&gt;&lt;td&gt;435 B &lt;/td&gt;&lt;td&gt;26.40% smaller&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Encode time &lt;/td&gt;&lt;td&gt;0.002 ms &lt;/td&gt;&lt;td&gt;0.009 ms &lt;/td&gt;&lt;td&gt;JSON faster&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Decode time &lt;/td&gt;&lt;td&gt;0.003 ms &lt;/td&gt;&lt;td&gt;0.008 ms &lt;/td&gt;&lt;td&gt;JSON faster&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gzip payload &lt;/td&gt;&lt;td&gt;377 B &lt;/td&gt;&lt;td&gt;371 B &lt;/td&gt;&lt;td&gt;1.59% smaller&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gzip compress time &lt;/td&gt;&lt;td&gt;0.009 ms &lt;/td&gt;&lt;td&gt;0.010 ms &lt;/td&gt;&lt;td&gt;Nearly equal&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Gzip decompress time &lt;/td&gt;&lt;td&gt;0.004 ms &lt;/td&gt;&lt;td&gt;0.003 ms &lt;/td&gt;&lt;td&gt;Nearly equal&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There are three useful conclusions here.&lt;/p&gt;
&lt;p&gt;First, MessagePack is noticeably smaller before compression for this fixture. That can matter when transfer size is the bottleneck.&lt;/p&gt;
&lt;p&gt;Second, the smallest representation is not automatically the fastest representation. For this small payload, JSON serialization is faster in the measured run.&lt;/p&gt;
&lt;p&gt;Third, compression changes the size comparison. Once gzip is applied, the gap is much smaller because gzip already removes a lot of JSON's repetitive syntax.&lt;/p&gt;
&lt;p&gt;These are indicative measurements, not universal promises. A real decision should use payloads, client runtimes, compression settings, and network conditions that match your application.&lt;/p&gt;
&lt;h3 id=&quot;decode-messagepack-requests&quot;&gt;Decode MessagePack requests&lt;/h3&gt;
&lt;p&gt;MessagePack request bodies are decoded automatically when the middleware is applied:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;use Illuminate\Http\Request;

Route::middleware('msgpack')-&amp;gt;post('/api/profile', function (Request $request) {
    return [
        'received' =&amp;gt; $request-&amp;gt;msgpack(),
        'name' =&amp;gt; $request-&amp;gt;input('name'),
    ];
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Map payloads are also merged into Laravel's normal request input. The original decoded value remains available through request()-&amp;gt;msgpack(), including scalar and list payloads.&lt;/p&gt;
&lt;p&gt;The package rejects malformed payloads before they reach the route handler. Configurable request size, nesting-depth, and value-count guards provide additional protection against oversized or unexpectedly deep input.&lt;/p&gt;
&lt;h3 id=&quot;when-a-route-must-always-return-messagepack&quot;&gt;When a route must always return MessagePack&lt;/h3&gt;
&lt;p&gt;Negotiation is useful when the same endpoint serves different clients. For a route that should always return MessagePack, use the response macro:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;return response()-&amp;gt;msgpack([
    'message' =&amp;gt; 'Created',
], 201, [
    'X-Request-Id' =&amp;gt; $requestId,
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Direct serialization is also available through the facade:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;use Msgpack;

$packed = Msgpack::encode($data);
$unpacked = Msgpack::decode($packed);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;a-practical-decision-guide&quot;&gt;A practical decision guide&lt;/h3&gt;
&lt;p&gt;Do not change every endpoint to MessagePack because a synthetic benchmark produced a smaller payload. Instead, measure the part of the system that matters.&lt;/p&gt;
&lt;p&gt;Choose JSON first when interoperability, browser support, observability, or simple debugging is the priority.&lt;/p&gt;
&lt;p&gt;Test MessagePack when bandwidth, payload volume, or mobile transfer cost is important and the clients are controlled.&lt;/p&gt;
&lt;p&gt;Keep both formats when you serve a mixed ecosystem. JSON remains the safe default, while clients that understand MessagePack can opt into it without a second controller implementation.&lt;/p&gt;
&lt;h3 id=&quot;limitations-to-keep-in-mind&quot;&gt;Limitations to keep in mind&lt;/h3&gt;
&lt;p&gt;MessagePack is not encryption. Sensitive data still needs transport security and application-level authorization.&lt;/p&gt;
&lt;p&gt;Binary responses are less convenient to inspect in logs and command-line terminals. Save them to a file or decode them with a client library.&lt;/p&gt;
&lt;p&gt;Compression may reduce the practical size advantage. Measure compressed and uncompressed responses for the transport configuration you actually deploy.&lt;/p&gt;
&lt;p&gt;The benchmark fixture is intentionally stable, not universal. A list-heavy payload, a text-heavy payload, a large nested object, or a response with repeated keys can produce different results.&lt;/p&gt;
&lt;h3 id=&quot;try-it-in-your-project&quot;&gt;Try it in your project&lt;/h3&gt;
&lt;p&gt;Install &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/smmehdisharifi/laravel-msgpack&quot;&gt;Laravel Msgpack&lt;/a&gt;, apply the msgpack middleware to one endpoint, and compare both representations with your real data.&lt;/p&gt;
&lt;p&gt;The repository contains the full implementation, compatibility matrix, configuration reference, safety limits, and benchmark command:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/smmehdisharifi/laravel-msgpack&quot;&gt;https://github.com/smmehdisharifi/laravel-msgpack&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If the package helps your API, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/smmehdisharifi/laravel-msgpack/stargazers&quot;&gt;a GitHub star&lt;/a&gt; helps other Laravel developers discover it.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/28486/json-vs-messagepack-in-laravel-a-practical-api-benchmark</guid>
<pubDate>Thu, 24 Sep 2026 13:39:50 +0000</pubDate>
</item>
<item>
<title>How to Use st-core.fscss with Svelte</title>
<link>https://coderlegion.com/27953/how-to-use-st-core-fscss-with-svelte</link>
<description>&lt;p&gt;st-core.fscss draws charts with pure CSS, &lt;code&gt;clip-path&lt;/code&gt; polygons plus custom properties, no JS chart library, no SVG, no canvas. Svelte doesn't need to draw anything for it. It just needs to own the data and reactively set &lt;code&gt;--st-p1&lt;/code&gt;, &lt;code&gt;--st-p2&lt;/code&gt;, and so on whenever your series changes. Wanted to write up the actual integration because getting the responsibilities split correctly took me a minute the first time through.&lt;/p&gt;
&lt;h4 id=&quot;why-compiled-mode-not-the-runtime-cdn&quot;&gt;Why compiled mode, not the runtime CDN&lt;/h4&gt;
&lt;p&gt;Svelte already has a build pipeline. Shipping the FSCSS runtime to a Svelte app just to expand a few chart mixins into static CSS is unnecessary work for the browser to do on every load. The runtime CDN script is fine for a CodePen or a quick static HTML demo, but for an actual Svelte or SvelteKit app you compile once at build time and import plain CSS.&lt;/p&gt;
&lt;p&gt;Three layers, each with one job:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;Job&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;FSCSS CLI &lt;/td&gt;&lt;td&gt;Expand &lt;code&gt;@st-chart-fill&lt;/code&gt; / &lt;code&gt;@st-chart-line&lt;/code&gt; into static CSS&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Svelte &lt;/td&gt;&lt;td&gt;Reactive &lt;code&gt;data[]&lt;/code&gt; → &lt;code&gt;--st-pN&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Browser CSS &lt;/td&gt;&lt;td&gt;Paint, plus optional &lt;code&gt;clip-path&lt;/code&gt; transitions&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Svelte state  →  style=&quot;--st-p1: 30%; --st-p2: 45%; …&quot;
                      ↓
              compiled st-core.css
                      ↓
                 chart on screen
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;1-write-the-fscss-entry-file&quot;&gt;1. Write the FSCSS entry file&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;src/styles/st-core.fscss&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from st-core@v2)

@st-root()

/* Length at compile time = number of polygon stops */
@arr chartData[0, 0, 0, 0, 0, 0, 0]

@st-chart-fill(.chart-fill, chartData)
@st-chart-line(.chart-line, chartData)

.chart {
  @st-chart-points(chartData)
  position: relative;
  width: 100%;
  height: 220px;
  background: var(--st-surface);
  border-radius: var(--st-radius-lg);
  overflow: hidden;
}

.chart-fill,
.chart-line {
  transition: clip-path 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The zeros are placeholders, they only matter for their count. Svelte overwrites the real Y values through CSS variables at runtime. What you can't change without recompiling is the array's &lt;strong&gt;length&lt;/strong&gt; — match it to your series (7 days of data → 7 entries).&lt;/p&gt;
&lt;h4 id=&quot;2-compile-it-as-part-of-your-build&quot;&gt;2. Compile it as part of your build&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -D fscss@latest
npx fscss src/styles/st-core.fscss src/styles/st-core.css
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wire it into &lt;code&gt;package.json&lt;/code&gt; so it runs automatically before every build:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;scripts&quot;: {
    &quot;build:st-core&quot;: &quot;fscss src/styles/st-core.fscss src/styles/st-core.css&quot;,
    &quot;prebuild&quot;: &quot;npm run build:st-core&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then import the compiled CSS once, wherever makes sense for your app (root layout, &lt;code&gt;app.css&lt;/code&gt;, or the chart's parent component):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import '../styles/st-core.css';
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-map-your-data-to---st-pn&quot;&gt;3. Map your data to &lt;code&gt;--st-pN&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Chart geometry in st-core is top-down, the same way CSS itself is. A high value needs to sit near &lt;code&gt;0%&lt;/code&gt;, not &lt;code&gt;100%&lt;/code&gt;. So every point gets inverted:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;--st-pᵢ = (100 - humanValue) %
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A score of &lt;code&gt;90&lt;/code&gt; ends up &lt;code&gt;10%&lt;/code&gt; from the top, which is exactly where you'd expect it visually.&lt;/p&gt;
&lt;p&gt;Here's a reusable &lt;code&gt;Chart.svelte&lt;/code&gt; that does that mapping reactively, Svelte 4 style:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-svelte&quot;&gt;&amp;lt;script&amp;gt;
  /** @type {number[]} 0–100, higher = taller */
  export let data = [];
  export let height = '200px';
  export let color = '#9d7eff';

  $: cssVars = [
    ...data.map((value, index) =&amp;gt; `--st-p${index + 1}: ${100 - value}%`),
    `--st-accent: ${color}`,
    `height: ${height}`
  ].join('; ');
&amp;lt;/script&amp;gt;

&amp;lt;div class=&quot;chart&quot; style={cssVars}&amp;gt;
  &amp;lt;div class=&quot;chart-fill&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;chart-line&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the Svelte 5 version, using &lt;code&gt;$props&lt;/code&gt; / &lt;code&gt;$derived&lt;/code&gt; instead of the old export-let / reactive-statement pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-svelte&quot;&gt;&amp;lt;script&amp;gt;
  let { data = [], height = '200px', color = '#9d7eff' } = $props();

  let cssVars = $derived(
    [
      ...data.map((value, index) =&amp;gt; `--st-p${index + 1}: ${100 - value}%`),
      `--st-accent: ${color}`,
      `height: ${height}`
    ].join('; ')
  );
&amp;lt;/script&amp;gt;

&amp;lt;div class=&quot;chart&quot; style={cssVars}&amp;gt;
  &amp;lt;div class=&quot;chart-fill&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;chart-line&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both do the exact same job, just with whichever reactivity API your Svelte version uses.&lt;/p&gt;
&lt;h4 id=&quot;4-use-it-like-any-other-component&quot;&gt;4. Use it like any other component&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-svelte&quot;&gt;&amp;lt;script&amp;gt;
  import './styles/st-core.css';
  import Chart from './lib/Chart.svelte';

  let weekly = [45, 70, 30, 90, 60, 80, 50];

  function randomize() {
    weekly = Array.from({ length: 7 }, () =&amp;gt; Math.floor(Math.random() * 80) + 10);
  }
&amp;lt;/script&amp;gt;

&amp;lt;Chart data={weekly} color=&quot;#4fffb0&quot; height=&quot;250px&quot; &amp;gt;

&amp;lt;!-- Svelte 4 --&amp;gt;
&amp;lt;button type=&quot;button&quot; on:click={randomize}&amp;gt;Randomize&amp;lt;/button&amp;gt;

&amp;lt;!-- Svelte 5 --&amp;gt;
&amp;lt;!-- &amp;lt;button type=&quot;button&quot; onclick={randomize}&amp;gt;Randomize&amp;lt;/button&amp;gt; --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reassigning &lt;code&gt;weekly&lt;/code&gt; recomputes &lt;code&gt;cssVars&lt;/code&gt;, the browser applies the new custom properties, and the compiled CSS's &lt;code&gt;clip-path&lt;/code&gt; transition animates the shape change. No chart library re-rendering, no SVG path recalculation, no manual DOM diffing on your end.&lt;/p&gt;
&lt;h4 id=&quot;fixed-length-vs-changing-length&quot;&gt;Fixed length vs changing length&lt;/h4&gt;
&lt;p&gt;Because geometry is baked in at compile time, the array length is the one thing you can't change on the fly:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Situation &lt;/th&gt;&lt;th&gt;What to do&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Always exactly &lt;strong&gt;N&lt;/strong&gt; points &lt;/td&gt;&lt;td&gt;Compile &lt;code&gt;@arr chartData&lt;/code&gt; with &lt;strong&gt;N&lt;/strong&gt; zeros, always pass exactly &lt;strong&gt;N&lt;/strong&gt; values from Svelte&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Length varies, up to a max of &lt;strong&gt;M&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Compile &lt;strong&gt;M&lt;/strong&gt; zeros, then &lt;code&gt;data.slice(0, M)&lt;/code&gt; and pad shorter series with &lt;code&gt;0&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you compile a template expecting 5 points and hand it 8 values from Svelte, the extra &lt;code&gt;--st-p6&lt;/code&gt;, &lt;code&gt;--st-p7&lt;/code&gt;, &lt;code&gt;--st-p8&lt;/code&gt; have nowhere to go, there's no polygon stop for them until you recompile with a longer array.&lt;/p&gt;
&lt;h4 id=&quot;what-not-to-do-in-production&quot;&gt;What not to do in production&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Fine for a demo, avoid as your only path in a real Svelte app --&amp;gt;
&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.2.3/runtime.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Runtime mode is for CodePen and quick static HTML, not for a build pipeline that already exists. See the plain &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss/tree/main/integration/html&quot;&gt;HTML integration sample&lt;/a&gt; if that's what you actually want.&lt;/p&gt;
&lt;h4 id=&quot;the-mental-model-condensed&quot;&gt;The mental model, condensed&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;@st-chart-points&lt;/code&gt;, fill, and line define the &lt;strong&gt;structure&lt;/strong&gt; and only ever read &lt;code&gt;--st-p*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Svelte's only job is to &lt;strong&gt;replace&lt;/strong&gt; those variables when the data changes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transition: clip-path&lt;/code&gt; gives you motion for free, no animation library needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nothing here is Svelte-specific past step 3, the same pattern maps directly to Vue, React, or vanilla JS, only the reactivity layer changes. Contributions for other frameworks welcome under &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss/tree/main/integration&quot;&gt;&lt;code&gt;integration/&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss&quot;&gt;github.com/fscss-ttr/st-core.fscss&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Svelte sample:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss/tree/main/integration/svelte&quot;&gt;integration/svelte&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27953/how-to-use-st-core-fscss-with-svelte</guid>
<pubDate>Mon, 21 Sep 2026 12:45:40 +0000</pubDate>
</item>
<item>
<title>The Silent Performance Killer: Demystifying the N+1 Query Problem in Node.js</title>
<link>https://coderlegion.com/27905/the-silent-performance-killer-demystifying-the-n-1-query-problem-in-node-js</link>
<description>&lt;p&gt;Have you ever noticed a feature that works beautifully on your local computer, only to crawl at a snail's pace once it gets deployed to production? One of the most common, yet invisible, reasons for this sudden slowdown is a database fetching behavior known as the N+1 query problem.&lt;/p&gt;
&lt;p&gt;In short, the N+1 query problem occurs when an application makes one initial database query to fetch a list of records (the &quot;1&quot;), and then makes a separate database query for every single record returned in that list (the &quot;N&quot;) to fetch related data. Instead of fetching all the required information in a single, efficient request, the application ends up chatting back and forth with the database dozens, hundreds, or thousands of times.&lt;/p&gt;
&lt;h4 id=&quot;the-restaurant-server-analogy&quot;&gt;The Restaurant Server Analogy&lt;/h4&gt;
&lt;p&gt;To understand why this is so inefficient, imagine sitting down at a busy restaurant with a table of ten friends. It is time to order drinks.&lt;/p&gt;
&lt;p&gt;An efficient server (who behaves like an optimized database query) would take everyone's drink orders on a notepad, walk to the kitchen once, load all ten glasses onto a single tray, and bring them back to the table in one trip. This requires just one round-trip to the kitchen.&lt;/p&gt;
&lt;p&gt;Now imagine an N+1 server. This server walks to the table, asks the first person what they want, and walks all the way back to the kitchen to fetch that single drink. After serving it, they walk back to the table, ask the second person for their order, and make another complete round-trip to the kitchen. They repeat this individual process for all ten people. By the time everyone has a drink, the server has made eleven total trips (1 trip to inspect the table, plus N trips for the N guests). Your dining experience would be incredibly slow, and the poor server would be completely exhausted.&lt;/p&gt;
&lt;h4 id=&quot;why-this-matters-in-daily-engineering&quot;&gt;Why This Matters in Daily Engineering&lt;/h4&gt;
&lt;p&gt;In real-world software engineering, database round-trips are remarkably expensive. Every single query you run introduces network latency, database parsing overhead, and thread consumption in your database's connection pool.&lt;/p&gt;
&lt;p&gt;If you are building a social media dashboard that displays 50 posts, and you load the comments for each post individually, you are executing 51 database queries. If 1,000 users visit that page simultaneously, your application will suddenly bombard your database with 51,000 queries. This easily exhausts your database connection pool, drives database CPU usage to 100%, and results in catastrophic timeouts for your users. Engineers must actively prevent this to keep application response times low and infrastructure costs manageable.&lt;/p&gt;
&lt;h4 id=&quot;seeing-it-in-action-node-js-and-mysql&quot;&gt;Seeing It in Action: Node.js and MySQL&lt;/h4&gt;
&lt;p&gt;Let's look at a typical Express.js endpoint where this mistake happens, followed by how we fix it.&lt;/p&gt;
&lt;p&gt;Here is the problematic, naive approach:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// BAD: This triggers N+1 queries
app.get('/api/posts', async (req, res) =&amp;gt; {
  try {
    // 1. Fetch all posts (The &quot;1&quot; query)
    const [posts] = await db.query('SELECT id, title, content FROM posts LIMIT 50');
    
    // 2. Loop through each post to fetch its comments (The &quot;N&quot; queries)
    for (let post of posts) {
      const [comments] = await db.query(
        'SELECT id, text FROM comments WHERE post_id = ?',
        [post.id]
      );
      post.comments = comments;
    }
    
    res.json(posts);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is how we resolve this using a SQL JOIN to pull all the information in a single, efficient query:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// GOOD: Resolved using a single JOIN query
app.get('/api/posts', async (req, res) =&amp;gt; {
  try {
    const query = `
      SELECT 
        p.id AS post_id, 
        p.title, 
        p.content, 
        c.id AS comment_id, 
        c.text AS comment_text
      FROM posts p
      LEFT JOIN comments c ON p.id = c.post_id
      LIMIT 50
    `;
    
    const [rows] = await db.query(query);
    
    // Group the flat SQL results into structured JSON
    const postsMap = {};
    for (const row of rows) {
      if (!postsMap[row.post_id]) {
        postsMap[row.post_id] = {
          id: row.post_id,
          title: row.title,
          content: row.content,
          comments: []
        };
      }
      if (row.comment_id) {
        postsMap[row.post_id].comments.push({
          id: row.comment_id,
          text: row.comment_text
        });
      }
    }
    
    res.json(Object.values(postsMap));
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-takeaway-1&quot;&gt;The Takeaway&lt;/h4&gt;
&lt;p&gt;The N+1 query problem highlights that how you fetch your data is just as important as the data itself. By switching from sequential, iterative loops to combined database operations like SQL JOINs, you minimize the highly expensive overhead of network round-trips. Designing your data fetching strategies proactively keeps your APIs incredibly fast and ensures your databases stay healthy, stable, and cost-efficient under high production loads.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/09/how-to-save-your-database-understanding.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27905/the-silent-performance-killer-demystifying-the-n-1-query-problem-in-node-js</guid>
<pubDate>Mon, 21 Sep 2026 09:18:48 +0000</pubDate>
</item>
<item>
<title>⚛️ Controlled vs Uncontrolled Components in React – A Deep Dive</title>
<link>https://coderlegion.com/27766/controlled-vs-uncontrolled-components-in-react-a-deep-dive</link>
<description>&lt;p&gt;When building forms in React, you’ll inevitably come across &lt;strong&gt;controlled&lt;/strong&gt; and &lt;strong&gt;uncontrolled&lt;/strong&gt; components.&lt;br&gt;
At first glance, they look similar — both accept user input and manage form data.&lt;br&gt;
But how they manage that data under the hood can significantly impact &lt;strong&gt;performance, maintainability and UX&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In this blog, let’s break down the difference, see them in action, explore real-world issues (like cursor position and undo/redo bugs), and understand when to use each.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-what-are-controlled-components-&quot;&gt;🧠 What Are Controlled Components?&lt;/h3&gt;
&lt;p&gt;In &lt;strong&gt;controlled components&lt;/strong&gt;, React state is the &lt;strong&gt;single source of truth&lt;/strong&gt;.&lt;br&gt;
That means every keystroke or change updates the component’s state and that state determines what’s rendered.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { useState } from &quot;react&quot;;

function ControlledInput() {
  const [name, setName] = useState(&quot;&quot;);

  return (
    &amp;lt;input
      value={name}
      onChange={(e) =&amp;gt; setName(e.target.value)}
      placeholder=&quot;Type your name&quot;
    /&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, the &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; value is fully controlled by React via the &lt;code&gt;name&lt;/code&gt; state.&lt;br&gt;
Each keystroke triggers &lt;code&gt;setName()&lt;/code&gt;, re-rendering the component with the updated value.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easy to validate or transform input on change.&lt;/li&gt;
&lt;li&gt;Centralized state makes debugging and unit testing easier.&lt;/li&gt;
&lt;li&gt;Integrates seamlessly with complex UIs or form libraries (Formik, React Hook Form).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Frequent re-renders can impact performance in large forms.&lt;/li&gt;
&lt;li&gt;Need to carefully manage performance and input lag.&lt;/li&gt;
&lt;li&gt;Cursor position bugs can occur with advanced state management.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-what-are-uncontrolled-components-&quot;&gt;🧩 What Are Uncontrolled Components?&lt;/h3&gt;
&lt;p&gt;In &lt;strong&gt;uncontrolled components&lt;/strong&gt;, the DOM maintains its own state — React just &lt;strong&gt;references&lt;/strong&gt; it.&lt;br&gt;
You don’t handle every keystroke; instead, you access the value when needed.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { useRef } from &quot;react&quot;;

function UncontrolledInput() {
  const inputRef = useRef();

  const handleSubmit = () =&amp;gt; {
    alert(`Input value: ${inputRef.current.value}`);
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;input ref={inputRef} placeholder=&quot;Type your name&quot; /&amp;gt;
      &amp;lt;button onClick={handleSubmit}&amp;gt;Submit&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ &lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Better performance for simple forms.&lt;/li&gt;
&lt;li&gt;Minimal React re-renders.&lt;/li&gt;
&lt;li&gt;Perfect when you just need the final value.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Harder to validate or modify values dynamically.&lt;/li&gt;
&lt;li&gt;Not ideal when input data must always reflect application state.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-controlled-vs-uncontrolled-key-differences&quot;&gt;⚔️ Controlled vs Uncontrolled: Key Differences&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature         &lt;/th&gt;&lt;th&gt;Controlled                 &lt;/th&gt;&lt;th&gt;Uncontrolled&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Data Source&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;React State                &lt;/td&gt;&lt;td&gt;DOM (via refs)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;May re-render often        &lt;/td&gt;&lt;td&gt;Faster for simple inputs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Validation&lt;/strong&gt;  &lt;/td&gt;&lt;td&gt;Easy to handle dynamically &lt;/td&gt;&lt;td&gt;Requires manual check&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Use Case&lt;/strong&gt;    &lt;/td&gt;&lt;td&gt;Complex, validated forms   &lt;/td&gt;&lt;td&gt;Simple or performance-critical inputs&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-valtio-amp-controlled-inputs-why-cursor-jumps-happen&quot;&gt;⚡ Valtio &amp;amp; Controlled Inputs: Why Cursor Jumps Happen&lt;/h3&gt;
&lt;p&gt;When you use a state library like Valtio in a React controlled input, you might encounter this issue: you edit text in the middle of the input, but the cursor jumps to the end. This frustrates users and breaks the typing experience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;🔍 What’s going on?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By default, Valtio batches updates before triggering re-renders. The documentation states:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“By default, state mutations are batched before triggering re-render. Sometimes, we want to disable the batching.”&lt;br&gt;
Because of this batching behavior, when you type and update the proxy state, multiple changes may be queued and then re-rendered in one go. During that delay, the browser loses context of the input cursor position, so after render the input’s value is applied and the cursor jumps to the end.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;✅ The fix: { sync: true }&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Valtio provides the option to disable batching for specific use-cases like inputs:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const snap = useSnapshot(state, { sync: true });&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;sync: true&lt;/code&gt;, updates go through immediately rather than being deferred/batched. This keeps the input cursor where the user expects it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Trade-off:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;sync: true&lt;/code&gt; disables batching optimizations, which can reduce render efficiency in large/complex components. Therefore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;sync: true&lt;/code&gt; only in input fields where cursor behaviour matters.&lt;/li&gt;
&lt;li&gt;Avoid it for general state where performance is more important than instantaneous update.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-undo-redo-cmd-z-cmd-shift-z-in-controlled-components&quot;&gt;🔁 Undo/Redo (Cmd+Z / Cmd+Shift+Z) in Controlled Components&lt;/h3&gt;
&lt;p&gt;Another subtle but important issue:&lt;br&gt;
Undo (&lt;code&gt;Cmd+Z&lt;/code&gt;) and Redo (&lt;code&gt;Cmd+Shift+Z&lt;/code&gt;) &lt;strong&gt;might not work&lt;/strong&gt; as expected in controlled inputs.&lt;/p&gt;
&lt;p&gt;Why?&lt;br&gt;
Because React replaces the entire value on every keystroke rather than letting the browser handle native input history.&lt;/p&gt;
&lt;p&gt;This means the browser sometimes &lt;strong&gt;loses the input undo stack&lt;/strong&gt;, especially if re-renders happen between updates.&lt;/p&gt;
&lt;p&gt;🧪 &lt;strong&gt;What to do:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Test undo/redo behavior in all input fields before handing over to QA.&lt;/li&gt;
&lt;li&gt;If using a state library or custom hooks, check whether it maintains the input history properly.&lt;/li&gt;
&lt;li&gt;In cases where it fails and browser-native undo is critical (e.g., long text editors), consider using &lt;strong&gt;uncontrolled inputs&lt;/strong&gt; or controlled wrappers that debounce updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-best-practices-for-react-forms&quot;&gt;🧰 Best Practices for React Forms&lt;/h3&gt;
&lt;p&gt;✅ Use &lt;strong&gt;controlled inputs&lt;/strong&gt; when you need validation, dynamic control or shared state.&lt;br&gt;
✅ Use &lt;strong&gt;uncontrolled inputs&lt;/strong&gt; when performance and simplicity matter.&lt;br&gt;
✅ For state libraries like &lt;strong&gt;Valtio&lt;/strong&gt;, test cursor and undo behaviors thoroughly.&lt;br&gt;
✅ Avoid unnecessary re-renders – use &lt;code&gt;React.memo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt; or split components.&lt;br&gt;
✅ Always test form UX — typing, cursor and undo/redo should feel &lt;strong&gt;native and fluid&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-final-thoughts&quot;&gt;🎯 Final Thoughts&lt;/h3&gt;
&lt;p&gt;Controlled vs Uncontrolled Components seem like a small React concept, but they can deeply impact user experience, especially in &lt;strong&gt;complex form-heavy applications&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;💡 Understanding these nuances (like cursor handling, undo/redo, and batching) helps you ship &lt;strong&gt;smoother, bug-free UIs&lt;/strong&gt; that feel professional and reliable.&lt;/p&gt;
&lt;p&gt;🚀 Whether you use plain React or libraries like Valtio, always test the real typing experience — that’s where users feel the difference.&lt;/p&gt;
&lt;p&gt;💬 Have you ever faced cursor or undo issues in your React forms? Drop your experience or workaround below — let’s learn together! 👇&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27766/controlled-vs-uncontrolled-components-in-react-a-deep-dive</guid>
<pubDate>Sat, 19 Sep 2026 19:08:29 +0000</pubDate>
</item>
<item>
<title>generateMetadata and Your Page Component Are Probably Querying the Same Data Twice</title>
<link>https://coderlegion.com/27732/generatemetadata-and-your-page-component-are-probably-querying-the-same-data-twice</link>
<description>&lt;p&gt;I wrote earlier about &lt;code&gt;cache()&lt;/code&gt; fixing redundant database queries when multiple components in a tree independently call the same query function. There's one specific, extremely common place this exact pattern shows up that's worth calling out directly, because it's not an edge case, it's the default shape of nearly every dynamic page with SEO metadata.&lt;/p&gt;
&lt;h3 id=&quot;the-setup-almost-every-dynamic-page-has&quot;&gt;The Setup Almost Every Dynamic Page Has&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/blog/[slug]/page.tsx
import { getPost } from '@/lib/queries/posts';

export async function generateMetadata({ params }) {
  const { slug } = await params;
  const post = await getPost(slug); // query #1

  return {
    title: post.title,
    description: post.excerpt,
  };
}

export default async function BlogPost({ params }) {
  const { slug } = await params;
  const post = await getPost(slug); // query #2, same post, same request

  return (
    &amp;lt;article&amp;gt;
      &amp;lt;h1&amp;gt;{post.title}&amp;lt;/h1&amp;gt;
      &amp;lt;div&amp;gt;{post.content}&amp;lt;/div&amp;gt;
    &amp;lt;/article&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the completely standard pattern from the Next.js metadata setup covered in an earlier post, and it's genuinely necessary, &lt;code&gt;generateMetadata&lt;/code&gt; needs the post's title and excerpt for the meta tags, the page component needs the post's full content to actually render it. Both run for every single request to this page. Both call &lt;code&gt;getPost(slug)&lt;/code&gt; independently. Without a specific fix, that's two full database round trips for what is, semantically, the exact same piece of data, fetched twice, for every single page view.&lt;/p&gt;
&lt;h3 id=&quot;why-this-specific-case-matters-more-than-it-might-seem&quot;&gt;Why This Specific Case Matters More Than It Might Seem&lt;/h3&gt;
&lt;p&gt;This isn't a rare pattern someone might occasionally write, it's the default shape of &lt;code&gt;generateMetadata&lt;/code&gt; combined with any dynamic page, which means it's quietly doubling database load on nearly every content-driven page across an entire site, blog posts, product pages, any dynamically rendered detail page with SEO metadata. A site with meaningful traffic on these pages is paying for twice the database queries it actually needs, on some of its most frequently visited pages, for no functional reason.&lt;/p&gt;
&lt;h3 id=&quot;the-fix-wrap-the-query-in-react-s-cache-&quot;&gt;The Fix: Wrap the Query in React's cache()&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// lib/queries/posts.ts
import { cache } from 'react';
import { connectDB } from '@/lib/db';
import Post from '@/models/Post';

export const getPost = cache(async (slug: string) =&amp;gt; {
  await connectDB();
  return Post.findOne({ slug }).lean();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's the entire fix, no changes needed anywhere else. &lt;code&gt;generateMetadata&lt;/code&gt; and the page component both still call &lt;code&gt;getPost(slug)&lt;/code&gt; exactly as before, the code looks identical. What changes is that React's request memoization now recognizes both calls, same function, same argument, within the same render pass, and only actually executes the underlying query once, returning the cached result for the second call.&lt;/p&gt;
&lt;h3 id=&quot;why-this-is-worth-checking-specifically-not-just-generally&quot;&gt;Why This Is Worth Checking Specifically, Not Just Generally&lt;/h3&gt;
&lt;p&gt;The earlier post on this topic covered the general pattern, multiple components in a tree calling the same query. This specific case is worth checking on its own because it's so easy to miss precisely because &lt;code&gt;generateMetadata&lt;/code&gt; and the page component don't look like they're part of the same &quot;component tree&quot; in the way nested components obviously are. They're two separate exported functions in the same file, and it's genuinely easy to write both, each looking completely correct in isolation, without ever noticing they're duplicating a database call for the same request.&lt;/p&gt;
&lt;h3 id=&quot;a-quick-way-to-verify-this-is-actually-happening&quot;&gt;A Quick Way to Verify This Is Actually Happening&lt;/h3&gt;
&lt;p&gt;Add a temporary log inside the un-cached query function and load the page once:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export async function getPost(slug: string) {
  console.log('getPost called for:', slug); // temporary
  await connectDB();
  return Post.findOne({ slug }).lean();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see that log line twice for a single page load, that's this exact issue, confirmed directly rather than assumed. After wrapping the function in &lt;code&gt;cache()&lt;/code&gt;, the same test should show it logging only once per request.&lt;/p&gt;
&lt;h3 id=&quot;where-this-doesn-t-apply&quot;&gt;Where This Doesn't Apply&lt;/h3&gt;
&lt;p&gt;If &lt;code&gt;generateMetadata&lt;/code&gt; and the page component genuinely need different data, metadata pulling from a lighter, separate summary endpoint while the page fetches full content from somewhere else entirely, there's no duplication to fix, since they're not actually calling the same underlying function with the same arguments in the first place. This fix specifically applies when both are calling the identical query function, which, for most blog and content-driven page setups, is exactly the common case.&lt;/p&gt;
&lt;h3 id=&quot;the-actual-rule-2&quot;&gt;The Actual Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Any query function called both inside &lt;code&gt;generateMetadata&lt;/code&gt; and inside the page component it belongs to should be wrapped in &lt;code&gt;cache()&lt;/code&gt;, as close to a universal default as a rule gets in this specific area.&lt;/strong&gt; It costs nothing to add, changes no calling code, and directly eliminates a real, silent, doubled database query that exists by default on essentially every dynamically rendered page with SEO metadata.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Go check any page in your own project using &lt;code&gt;generateMetadata&lt;/code&gt; alongside a matching page component, specifically whether the underlying query function is wrapped in &lt;code&gt;cache()&lt;/code&gt;. If it's not, you're very likely double-querying on every single page load for that route. Drop what you find in the comments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27732/generatemetadata-and-your-page-component-are-probably-querying-the-same-data-twice</guid>
<pubDate>Sat, 19 Sep 2026 10:48:58 +0000</pubDate>
</item>
<item>
<title>Code Fingerprinting: Detecting Duplicate Submissions Without Losing Your Mind (or Your API Budget)</title>
<link>https://coderlegion.com/27702/code-fingerprinting-detecting-duplicate-submissions-without-losing-your-mind-your-budget</link>
<description>&lt;p&gt;While building LogicVisor's review system, I was faced with a question I hadn't thought about before: what would happen if users submitted duplicate algorithm solutions?&lt;/p&gt;
&lt;p&gt;I decided to test it myself. I submitted a solution to a simple palindrome challenge. The request went through, my solution was reviewed correctly. Normal happy stuff. Then I submitted the exact same solution a second time. And the normal happy stuff happened again. But I was far from happy.&lt;/p&gt;
&lt;p&gt;That duplicate submission meant more tokens spent, more database memory consumed, more remote procedure calls, more AI calls, and more waiting time. For a problem that had already been solved.&lt;/p&gt;
&lt;p&gt;I shuddered at the thought of a bad actor effectively DOS-ing my site or running up my AI bill just by hammering the same solution repeatedly. Rate limiting controls the number of requests per client — but it has nothing to do with the &lt;em&gt;content&lt;/em&gt; of those requests. A user could stay well within their rate limit and still abuse the system by submitting the same code over and over.&lt;/p&gt;
&lt;p&gt;This wasn't going to work. I needed a way to identify duplicate submissions and return a cached response instead of processing everything from scratch. Which led me to a more interesting question: how do you actually detect identical code?&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-with-identical-&quot;&gt;The Problem With &quot;Identical&quot;&lt;/h3&gt;
&lt;p&gt;Programming by its very nature allows us to solve the same problem in multiple ways. But even a single solution can be written in an infinite number of ways.&lt;/p&gt;
&lt;p&gt;User A could create a function that takes and returns a string, and name it &lt;code&gt;strawberries&lt;/code&gt;. User B could write the exact same function with the exact same logic and name it &lt;code&gt;bananas&lt;/code&gt;. Different names, same implementation, same result.&lt;/p&gt;
&lt;p&gt;For LogicVisor's purposes, these should be counted as duplicates. But how do you get a computer to see past the surface differences and recognize that two pieces of code are structurally the same?&lt;/p&gt;
&lt;p&gt;I started thinking about it from first principles. At the end of the day, programming languages get compiled down to binary and then to electrical pulses. If two solutions solve the same problem the same way, they're ultimately firing the same set of pulses at the machine level. Variable names be damned.&lt;/p&gt;
&lt;p&gt;So the question became: can I strip a solution down to its most fundamental representation and compare &lt;em&gt;that&lt;/em&gt;?&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;enter-the-abstract-syntax-tree&quot;&gt;Enter the Abstract Syntax Tree&lt;/h3&gt;
&lt;p&gt;More research led me to the Abstract Syntax Tree — something my OS professor had mentioned in school that I'd filed away as &quot;probably never useful in web dev.&quot; No knowledge is wasted.&lt;/p&gt;
&lt;p&gt;An Abstract Syntax Tree (AST) is a hierarchical, tree-shaped data structure that represents the syntactic structure of source code. It strips away superficial syntax — semicolons, curly braces, whitespace, comments — and keeps only the structural and semantic skeleton of the code. The &lt;em&gt;shape&lt;/em&gt; of what you wrote, not the clothing it's wearing.&lt;/p&gt;
&lt;p&gt;This was it. But I hit a wall almost immediately: I can't store and index an entire AST reliably in PostgreSQL. It's a complex nested structure, not a scalar value you can slap an index on.&lt;/p&gt;
&lt;p&gt;Then I remembered an article about handling duplicate file uploads in Express with Multer. The trick was generating a SHA-256 hash of the file's content. A hash is a fixed-length fingerprint — the same input always produces the same output, and different inputs produce different outputs. Perfect.&lt;/p&gt;
&lt;p&gt;The plan crystallized:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Parse the submitted code into an AST&lt;/li&gt;
&lt;li&gt;Normalize the AST (rename variables, strip comments, etc.)&lt;/li&gt;
&lt;li&gt;Hash the normalized output with SHA-256&lt;/li&gt;
&lt;li&gt;Store and index that hash&lt;/li&gt;
&lt;li&gt;On every new submission, compute the hash and check if it already exists&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the hash is in the database, return the cached review. No AI call. No wasted tokens. No waiting.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;two-parsers-for-the-price-of-one&quot;&gt;Two Parsers for the Price of One&lt;/h3&gt;
&lt;p&gt;LogicVisor supports eight languages: JavaScript, TypeScript, Python, Java, C, C++, Go, and Rust. Getting an AST for all of them required two different tools, each with their own approach to the world.&lt;/p&gt;
&lt;h4 id=&quot;babel-parser-for-javascript-and-typescript-&quot;&gt;Babel Parser (for JavaScript and TypeScript)&lt;/h4&gt;
&lt;p&gt;Babel is something most JavaScript developers know as a transpiler — the thing that converts modern JS to something older browsers can run. But under the hood, Babel has a full-featured parser that produces a detailed AST of your JavaScript or TypeScript code. The &lt;code&gt;@babel/parser&lt;/code&gt; package exposes this directly.&lt;/p&gt;
&lt;p&gt;I'd used Babel's &lt;em&gt;output&lt;/em&gt; for years but never touched the parser API directly. It's surprisingly clean. You hand it source code and options, and it gives you back a structured AST you can traverse and manipulate.&lt;/p&gt;
&lt;p&gt;What made Babel the right choice for JS/TS specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Native TypeScript support via a plugin — no separate toolchain&lt;/li&gt;
&lt;li&gt;JSX support out of the box&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;@babel/traverse&lt;/code&gt; package lets you walk the AST and modify nodes in place, which is exactly how I rename identifiers&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@babel/generator&lt;/code&gt; converts the modified AST back to compact source code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The JS/TS canonicalization path: parse with Babel → traverse the AST and rename all non-builtin identifiers to generic names (&lt;code&gt;v0&lt;/code&gt;, &lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;...) → optionally sort top-level declarations → regenerate compact source → hash it.&lt;/p&gt;
&lt;h4 id=&quot;tree-sitter-for-everything-else-&quot;&gt;Tree-sitter (for everything else)&lt;/h4&gt;
&lt;p&gt;Tree-sitter is a different beast. It's a parser &lt;em&gt;generator&lt;/em&gt; — a tool for building fast, incremental, error-tolerant parsers for programming languages. You might have encountered it without knowing it: if you've used Neovim with syntax highlighting that actually understands your code structure (my first encounter with the tool), that's Tree-sitter. Same if you've used editors where renaming a variable highlights every reference correctly. It's doing real parsing, not just regex matching.&lt;/p&gt;
&lt;p&gt;What makes Tree-sitter interesting for this use case is that it has community-maintained grammars for a huge range of languages — C, C++, Go, Rust, Java, Python, and many more. Each grammar is a separate package that teaches Tree-sitter how to parse that language. You install the grammar, set it on a parser instance, and Tree-sitter gives you a concrete syntax tree you can walk.&lt;/p&gt;
&lt;p&gt;I hadn't really understood what Tree-sitter &lt;em&gt;was&lt;/em&gt; before this. I'd seen it referenced in Neovim config files and assumed it was some Vim-specific thing. It's not — it's a general purpose parsing library with bindings for multiple languages including Node.js via the &lt;code&gt;node-tree-sitter&lt;/code&gt; package.&lt;/p&gt;
&lt;p&gt;The Tree-sitter path: parse the source → walk the resulting syntax tree with a DFS traversal → collect all identifier nodes by their byte ranges → build a name mapping (first-occurrence order) → apply replacements from the end of the file backwards (so byte indices don't shift) → normalize whitespace → hash it.&lt;/p&gt;
&lt;p&gt;The backwards-replacement trick is worth calling out. When you're doing string replacements by index, if you replace something early in the file, every subsequent index is now wrong. By sorting replacements by start position descending and working from the end, you avoid that entire class of bug.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-implementation&quot;&gt;The Implementation&lt;/h3&gt;
&lt;p&gt;Here's how it all fits together.&lt;/p&gt;
&lt;h4 id=&quot;the-simple-canonicalization-pre-ast-&quot;&gt;The Simple Canonicalization (Pre-AST)&lt;/h4&gt;
&lt;p&gt;Before I landed on the AST approach, I had a simpler version that just handled whitespace and comments:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export function canonicalizeCode(code: string): string {
  // Remove multi-line comments
  let cleanedCode = code.replace(/\/\*[\s\S]*?\*\//g, &quot;&quot;);

  // Remove single-line comments
  cleanedCode = cleanedCode.replace(/\/\/.*$/gm, &quot;&quot;);

  // Normalize all whitespace to a single space
  cleanedCode = cleanedCode.replace(/\s+/g, &quot; &quot;).trim();

  return cleanedCode;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This catches the obvious cases — same code with different formatting or comments. But it falls apart the moment someone renames a variable. The AST approach handles that.&lt;/p&gt;
&lt;h4 id=&quot;the-router&quot;&gt;The Router&lt;/h4&gt;
&lt;p&gt;The main entry point checks the language and dispatches to the right parser:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export async function canonicalizeCodeAST(
  code: string,
  language: LanguageKey,
  options?: CanonicalizeOptions
): Promise&amp;lt;string&amp;gt; {
  const opts = { ...defaultOptions, ...(options || {}) };

  if (language === &quot;javascript&quot; || language === &quot;typescript&quot;) {
    return canonicalizeJS(code, opts);
  }

  // C/C++/Go/Rust/Java/Python go through Tree-sitter
  return canonicalizeWithTreeSitter(code, language, null);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-babel-path-js-ts-&quot;&gt;The Babel Path (JS/TS)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function canonicalizeJS(
  code: string,
  opts: Required&amp;lt;CanonicalizeOptions&amp;gt;
): string {
  const plugins: any[] = [&quot;jsx&quot;];
  if (opts) plugins.push(&quot;typescript&quot;);

  const ast = babelParser.parse(code, {
    sourceType: &quot;module&quot;,
    plugins,
    allowReturnOutsideFunction: true,
  });

  // Rename all non-builtin identifiers to generic names
  const nameMap = new Map&amp;lt;string, string&amp;gt;();
  let counter = 0;

  traverse(ast as any, {
    Identifier(path) {
      if (!opts.normalizeVariableNames) return;

      const name = path.node.name;
      if (isBuiltinJsName(name)) return;

      if (!nameMap.has(name)) {
        nameMap.set(name, `v${counter++}`);
      }

      path.node.name = nameMap.get(name)!;
    },
  });

  // Optionally sort top-level declarations for order-independent comparison
  if (
    opts.sortTopLevel &amp;amp;&amp;amp;
    (ast as any).program &amp;amp;&amp;amp;
    Array.isArray((ast as any).program.body)
  ) {
    (ast as any).program.body.sort((a: any, b: any) =&amp;gt; {
      const aStr = generate(a).code;
      const bStr = generate(b).code;
      return aStr.localeCompare(bStr);
    });
  }

  // Regenerate compact source from the modified AST
  return generate(ast as any, {
    comments: !opts.removeComments,
    compact: true,
  }).code;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-tree-sitter-path-everything-else-&quot;&gt;The Tree-sitter Path (Everything Else)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function canonicalizeWithTreeSitter(
  code: string,
  language: LanguageKey,
  opts: Required&amp;lt;CanonicalizeOptions&amp;gt; | null
): string {
  const LangCtor = (treeSitterMap as any)[language];

  if (!LangCtor) {
    throw new Error(`Unsupported language for tree-sitter: ${language}`);
  }

  const parser = new Parser();
  parser.setLanguage(LangCtor);

  const tree = parser.parse(code);
  const root = tree.rootNode;

  const identifierNodeTypes = identifierTypeMap[language] || [&quot;identifier&quot;];
  const idNodes: { start: number; end: number; text: string }[] = [];
  const commentNodes: { start: number; end: number }[] = [];

  // DFS traversal to collect identifier and comment nodes
  const stack = [root];
  while (stack.length) {
    const node = stack.pop()!;
    const type = node.type;

    if (type.includes(&quot;comment&quot;) &amp;amp;&amp;amp; opts?.removeComments) {
      commentNodes.push({ start: node.startIndex, end: node.endIndex });
    }

    if (identifierNodeTypes.includes(type) || /identifier|name/i.test(type)) {
      const text = code.slice(node.startIndex, node.endIndex);
      idNodes.push({ start: node.startIndex, end: node.endIndex, text });
    }

    for (let i = 0; i &amp;lt; node.childCount; i++) {
      stack.push(node.child(i) as SyntaxNode);
    }
  }

  // Build name mapping in first-occurrence order
  const nameMap = new Map&amp;lt;string, string&amp;gt;();
  let counter = 0;

  for (const n of idNodes) {
    const orig = n.text;
    if (isLanguageBuiltinName(orig, language)) continue;
    if (!nameMap.has(orig)) {
      nameMap.set(orig, `v${counter++}`);
    }
  }

  // Build replacement list
  const replacements: { start: number; end: number; replacement: string }[] = [];

  if (opts?.removeComments) {
    for (const c of commentNodes) {
      replacements.push({ start: c.start, end: c.end, replacement: &quot;&quot; });
    }
  }

  if (opts?.normalizeVariableNames) {
    for (const id of idNodes) {
      const mapped = nameMap.get(id.text);
      if (mapped) {
        replacements.push({ start: id.start, end: id.end, replacement: mapped });
      }
    }
  }

  // Apply replacements from end to start so byte indices stay valid
  replacements.sort((a, b) =&amp;gt; b.start - a.start);

  let out = code;
  for (const r of replacements) {
    out = out.slice(0, r.start) + r.replacement + out.slice(r.end);
  }

  return out.trim();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;hashing-the-canonical-form&quot;&gt;Hashing the Canonical Form&lt;/h4&gt;
&lt;p&gt;Once we have the normalized source, hashing is straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export async function createCanonicalHash(canonicalCode: string): Promise&amp;lt;string&amp;gt; {
  const encoder = new TextEncoder();
  const data = encoder.encode(canonicalCode);
  const hashBuffer = await crypto.subtle.digest(&quot;SHA-256&quot;, data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map((b) =&amp;gt; b.toString(16).padStart(2, &quot;0&quot;)).join(&quot;&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SHA-256 gives us a fixed 64-character hex string regardless of how long the original code was. That's what gets stored and indexed in PostgreSQL.&lt;/p&gt;
&lt;h4 id=&quot;putting-it-all-together-in-the-request-handler&quot;&gt;Putting It All Together in the Request Handler&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;let canonicalCode;
try {
  canonicalCode = await canonicalizeCodeAST(solution, preferred_language);
} catch (error) {
  console.error(&quot;Canonicalization error:&quot;, error);
  return NextResponse.json(
    {
      success: false,
      error: {
        code: &quot;BAD_USER_INPUT&quot;,
        message: &quot;Unable to parse your code. Please check for syntax errors and try again.&quot;,
      },
    },
    { status: 400 }
  );
}

const canonicalHash = await createCanonicalHash(
  typeof canonicalCode === &quot;string&quot; ? canonicalCode : &quot;&quot;
);

// Check cache before touching the AI provider
const cachedReview = await getCachedAIReview(
  preferred_model.id + &quot;-&quot; + canonicalHash
);

if (cachedReview) {
  return NextResponse.json(
    { success: true, data: cachedReview },
    { status: 201 }
  );
}

// No cache hit — proceed with AI review and store the result
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The model ID is included in the cache key because the same code reviewed by Gemini and Groq will produce different outputs. You don't want to serve a Gemini review to someone who selected Groq.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;does-it-actually-work-&quot;&gt;Does It Actually Work?&lt;/h3&gt;
&lt;p&gt;Here's the test that proved it out. Five TypeScript implementations of the same palindrome algorithm — different variable names, different formatting, different comments, and one with operand order swapped:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Solution 1 — clean, descriptive names
function isPalindrome(x: number): boolean {
  let originalNum = x;
  let reversedNum = 0;
  while (originalNum &amp;gt; 0) {
    let lastDigit = originalNum % 10;
    reversedNum = (reversedNum * 10) + lastDigit;
    originalNum = Math.floor(originalNum / 10);
  }
  return x === reversedNum;
}

// Solution 2 — different variable names
function isPalindrome(n: number): boolean {
  let temp = n;
  let result = 0;
  while (temp &amp;gt; 0) {
    let digit = temp % 10;
    result = (result * 10) + digit;
    temp = Math.floor(temp / 10);
  }
  return n === result;
}

// Solution 3 — compressed formatting + comments
function isPalindrome(x: number): boolean{
  // Store original
  let originalNum=x;let reversedNum=0;
  while(originalNum&amp;gt;0){
    let lastDigit=originalNum%10; /* get digit */
    reversedNum=(reversedNum*10)+lastDigit;
    originalNum=Math.floor(originalNum/10);
  }
  return x===reversedNum;
}

// Solution 4 — addition operands swapped
function isPalindrome(x: number): boolean {
  let originalNum = x;
  let reversedNum = 0;
  while (originalNum &amp;gt; 0) {
    let lastDigit = originalNum % 10;
    reversedNum = lastDigit + (reversedNum * 10); // addition order changed
    originalNum = Math.floor(originalNum / 10);
  }
  return reversedNum === x; // comparison order changed
}

// Solution 5 — completely different algorithm
function isPalindrome(x: number): boolean {
  const str = x.toString();
  return str === str.split('').reverse().join('');
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the test harness:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export async function testDuplicateDetection() {
  const solutions = [ /* ...the five solutions above... */ ];

  console.log(&quot;Code hash comparison:&quot;);

  solutions.forEach(async (code, index) =&amp;gt; {
    const canonicalized = await canonicalizeCodeAST(code, &quot;typescript&quot;);
    const hash = await createCanonicalHash(canonicalized);
    console.log(`\nSolution ${index + 1}:`);
    console.log(`Hash: ${hash.substring(0, 12)}...`);
    console.log(
      `Canonical: ${
        typeof canonicalized === &quot;string&quot; ? canonicalized.substring(0, 100) : &quot;&quot;
      }...`,
    );
  });

  // Canonicalize before hashing — not after
  const hashes = await Promise.all(
    solutions.map(async (code) =&amp;gt; {
      const canonicalized = await canonicalizeCodeAST(code, &quot;typescript&quot;);
      return createCanonicalHash(canonicalized);
    }),
  );
  const uniqueHashes = new Set(hashes);

  console.log(`\nOriginal solutions: ${solutions.length}`);
  console.log(`Unique hashes: ${uniqueHashes.size}`);
  console.log(`Duplicates detected: ${solutions.length - uniqueHashes.size}`);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;Code hash comparison:

Solution 1:
Hash: f7142f71e35a...
Canonical: function v0(v1:number):boolean{let v2=v1;let v3=0;while(v2&amp;gt;0){let v4=v2%10;v3=v3*10+v4;v2=Math.v5(v2...

Solution 2:
Hash: f7142f71e35a...
Canonical: function v0(v1:number):boolean{let v2=v1;let v3=0;while(v2&amp;gt;0){let v4=v2%10;v3=v3*10+v4;v2=Math.v5(v2...

Solution 3:
Hash: f7142f71e35a...
Canonical: function v0(v1:number):boolean{let v2=v1;let v3=0;while(v2&amp;gt;0){let v4=v2%10;v3=v3*10+v4;v2=Math.v5(v2...

Solution 4:
Hash: da8268fe24dd...
Canonical: function v0(v1:number):boolean{let v2=v1;let v3=0;while(v2&amp;gt;0){let v4=v2%10;v3=v4+v3*10;v2=Math.v5(v2...

Solution 5:
Hash: e3d2696010ad...
Canonical: function v0(v1:number):boolean{const v2=v1.v3();return v2===v2.v4('').v5().v6('');}...

Original solutions: 5
Unique hashes: 3
Duplicates detected: 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dhyxcd5wx7i8up034dbi.png&quot; alt=&quot;Console screenshot of duplicate detection test results&quot;&gt;&lt;/p&gt;
&lt;p&gt;Solutions 1, 2, and 3 collapse to the same hash — variable names, whitespace, and comments are all stripped away during canonicalization, leaving identical ASTs. Solution 4 gets a distinct hash because operand order (lastDigit + reversedNum &lt;em&gt; 10 vs reversedNum &lt;/em&gt; 10 + lastDigit) is a syntactic difference the AST preserves — commutativity is math, not structure, and the canonicalizer doesn't evaluate expressions. Solution 5 is correctly identified as a genuinely different algorithm.&lt;br&gt;
Two duplicates detected. Working as intended.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-tradeoffs-worth-knowing&quot;&gt;The Tradeoffs Worth Knowing&lt;/h3&gt;
&lt;p&gt;This approach isn't magic. A few things it doesn't catch:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Semantic equivalence across algorithms.&lt;/strong&gt; Two completely different implementations that produce the same result — bubble sort vs. merge sort, for example — will have different hashes. This system detects structural duplicates, not algorithmic equivalence. That's actually the right behavior for a code review platform.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Heuristic identifier detection in Tree-sitter.&lt;/strong&gt; The identifier node types vary between language grammars, and I'm using pattern matching (&lt;code&gt;/identifier|name/i&lt;/code&gt;) as a fallback. It works well in practice but it's not exhaustive. If a grammar names its identifier nodes something unusual, you might miss some.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Builtin lists are incomplete.&lt;/strong&gt; The &lt;code&gt;isBuiltinJsName&lt;/code&gt; and &lt;code&gt;isLanguageBuiltinName&lt;/code&gt; functions cover the most common builtins but aren't exhaustive. If a user names their variable &lt;code&gt;fetch&lt;/code&gt; or &lt;code&gt;setTimeout&lt;/code&gt;, those won't be normalized. Not a critical flaw, but worth knowing.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-took-away-from-this&quot;&gt;What I Took Away From This&lt;/h3&gt;
&lt;p&gt;The part I didn't expect: how much I'd enjoy it. This was one of those problems that pulled me away from the normal web dev loop of &quot;call an API, render some data&quot; and into something that felt closer to computer science. Parsers, ASTs, tree traversal — things I'd learned in school that felt abstract at the time suddenly had a real home in a real production system.&lt;/p&gt;
&lt;p&gt;The other thing I took away: sometimes the right caching strategy isn't about time-to-live or cache invalidation. Sometimes it's about recognizing that two requests are asking for the same thing, even when they look different on the surface. That realization changed how I thought about a whole class of optimization problems.&lt;/p&gt;
&lt;p&gt;If you're building anything that takes code as input — a linter, a reviewer, a plagiarism detector, an interview platform — the AST + hash pattern is worth having in your toolkit.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;LogicVisor is an AI-powered tool that reviews your algorithmic code, breaks down time and space complexity, and gives you the kind of feedback you'd want before a technical interview. You can try it at &lt;a rel=&quot;nofollow&quot; href=&quot;https://logicvisor.vercel.app&quot;&gt;logicvisor.vercel.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/27702/code-fingerprinting-detecting-duplicate-submissions-without-losing-your-mind-your-budget</guid>
<pubDate>Sat, 19 Sep 2026 07:49:18 +0000</pubDate>
</item>
<item>
<title>Finally Shipped st-core.fscss v2 — CSS Charts Without the 8-Point Ceiling</title>
<link>https://coderlegion.com/27679/finally-shipped-st-core-fscss-v2-css-charts-without-the-8-point-ceiling</link>
<description>&lt;p&gt;For a while, st-core.fscss charts had one annoying limit: every line, fill, and dot was locked to exactly 8 data points. &lt;code&gt;--st-p1&lt;/code&gt; through &lt;code&gt;--st-p8&lt;/code&gt;, hardcoded. Want to plot 12 weeks of revenue? Tough. Want 5 points for a quick demo? You'd pad the array with zeros and hope nobody noticed.&lt;/p&gt;
&lt;p&gt;v2 gets rid of that.&lt;/p&gt;
&lt;h4 id=&quot;the-core-change-array-arr-&quot;&gt;The core change: Array (&lt;code&gt;@arr&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;Instead of passing 8 raw numbers into &lt;code&gt;@st-chart-points&lt;/code&gt;, you now declare a named array of any length:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@arr myData[50, 10, 97, 35, 66, 50, 80, 54, 70, 60]

@st-chart-fill(.chart-fill, myData)
@st-chart-line(.chart-line, myData)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;st-core reads the array length at compile time, works out even X-spacing across the chart width, and builds the &lt;code&gt;clip-path: polygon()&lt;/code&gt; shape from however many points you gave it. 5 points, 12 points, 50 points, doesn't matter. No more counting to 8.&lt;/p&gt;
&lt;h4 id=&quot;the-gotcha-that-took-me-a-minute-to-internalize&quot;&gt;The gotcha that took me a minute to internalize&lt;/h4&gt;
&lt;p&gt;Here's the part that actually tripped me up while building the multi-series examples: &lt;code&gt;@st-chart-line(selector, array)&lt;/code&gt; and &lt;code&gt;@st-chart-fill(selector, array)&lt;/code&gt; aren't what set the data on an element. They declare the renderer for that selector, the array argument just tells it how many points to expect.&lt;/p&gt;
&lt;p&gt;The thing that actually writes &lt;code&gt;--st-p1&lt;/code&gt;…&lt;code&gt;--st-p{n}&lt;/code&gt; onto an element is &lt;code&gt;@st-chart-points(array)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That distinction matters the moment you have more than one series on a chart. In a multi-line setup, if &lt;code&gt;.line-b&lt;/code&gt; doesn't call &lt;code&gt;@st-chart-points(seriesB)&lt;/code&gt; itself, it silently inherits whatever the parent container set, meaning it renders the wrong dataset. Looks fine at a glance, wrong shape in practice. Caught it while building a 3-series dashboard card where two lines were quietly drawing the same data.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.fill-b {
  @st-chart-points(seriesB)   /* required, not optional */
  opacity: 0.3;
  --st-accent: #4fffb0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once that clicked, the mental model got simple: renderer declarations are structural (once per selector), point data is per-element (as many times as you have distinct series).&lt;/p&gt;
&lt;h4 id=&quot;how-it-feels-to-ship&quot;&gt;How it feels to ship&lt;/h4&gt;
&lt;p&gt;Pure CSS charting was already a fun constraint to design inside of, no JS runtime, no SVG, no canvas. Making the dataset size flexible removes the last thing that made it feel like a toy rather than something you'd reach for on a real dashboard. Compiled output is still under a kilobyte. Still zero hydration.&lt;/p&gt;
&lt;p&gt;Docs and full examples are up in the repo, including the legacy v1 reference for anyone still on fixed 8-point charts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss&quot;&gt;github.com/fscss-ttr/st-core.fscss&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Open-source&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;with ♥️&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss/blob/main/st-core%40v2.fscss&quot;&gt;source code&lt;/a&gt; is totally math notebook 😅&lt;/p&gt;
&lt;p&gt;Have been writing and debugging from morning till night, and finally released it. The usage is now as simple as CSS.&lt;/p&gt;
&lt;p&gt;😊&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27679/finally-shipped-st-core-fscss-v2-css-charts-without-the-8-point-ceiling</guid>
<pubDate>Sat, 19 Sep 2026 01:08:17 +0000</pubDate>
</item>
<item>
<title>Stop Crashing Your Database: A Developer's Guide to Connection Pooling</title>
<link>https://coderlegion.com/27378/stop-crashing-your-database-a-developers-guide-to-connection-pooling</link>
<description>&lt;p&gt;Database connections are the lifeblood of any web application, but they are also surprisingly expensive to create and destroy. When your application struggles under sudden traffic spikes, the database is often the first thing to break. This is where connection pooling comes in.&lt;/p&gt;
&lt;p&gt;Connection pooling is a software development technique that creates and maintains a cache of active database connections, keeping them ready for immediate reuse. Instead of initiating and closing a brand-new connection every time a user requests data, your application borrows an already-established connection from this pre-allocated &quot;pool.&quot; Once the query is complete, the connection is returned to the pool for the next request to use, rather than being discarded.&lt;/p&gt;
&lt;h4 id=&quot;the-taxi-stand-analogy&quot;&gt;The Taxi Stand Analogy&lt;/h4&gt;
&lt;p&gt;To understand this clearly, think of a busy taxi stand outside a major hotel. If every guest who wanted to travel into town had to call a car dealership, buy a brand-new car, register it with the city, drive to their destination, and then crush the vehicle at a scrap yard when they arrived, transportation would be impossibly slow, expensive, and wasteful. &lt;/p&gt;
&lt;p&gt;Instead, the hotel relies on a taxi stand. A small fleet of vehicles sits idling in the driveway. When a guest steps outside, they instantly hop into an available taxi, take their trip, and the driver returns to the stand to wait for the next passenger. The taxi is never destroyed; it is constantly reused. In this analogy, the guests are your incoming API requests, the taxis are your database connections, and the stand is your connection pool.&lt;/p&gt;
&lt;h4 id=&quot;why-connection-pooling-matters-daily&quot;&gt;Why Connection Pooling Matters Daily&lt;/h4&gt;
&lt;p&gt;In real-world software engineering, establishing a database connection is not a simple task. It requires a multi-step process: initiating a TCP handshake over the network, negotiating security protocols (TLS/SSL), authenticating the database user's credentials, allocating memory on the database server, and setting up session variables. Doing this on every single HTTP request adds massive latency to your API responses.&lt;/p&gt;
&lt;p&gt;Furthermore, databases have strict physical limits. Every active connection consumes a slice of the database server's RAM and CPU. If your Express server experiences a sudden burst of 500 concurrent users and tries to open 500 individual connections simultaneously, your MySQL database will quickly run out of memory. This leads to the dreaded &quot;Too many connections&quot; error, bringing your entire application to a grinding halt. Connection pooling caps the maximum number of connections, forcing extra requests to wait in a fast queue rather than crashing your database server.&lt;/p&gt;
&lt;h4 id=&quot;putting-it-into-code&quot;&gt;Putting It into Code&lt;/h4&gt;
&lt;p&gt;Here is how you implement connection pooling in a Node.js application using the popular &lt;code&gt;mysql2&lt;/code&gt; driver. Instead of using &lt;code&gt;createConnection&lt;/code&gt;, we use &lt;code&gt;createPool&lt;/code&gt; to manage our resources efficiently.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const mysql = require('mysql2/promise');

// Configure and create the connection pool
const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'your_secure_password',
  database: 'production_db',
  waitForConnections: true,    // Queue requests when pool is full
  connectionLimit: 10,         // Max number of simultaneous active connections
  queueLimit: 0                // No limit on waiting requests (0 means infinite)
});

// Helper function to fetch a user profile
async function getUserProfile(userId) {
  try {
    // The pool automatically checks out a connection, runs the query, and returns it
    const [rows] = await pool.execute(
      'SELECT id, username, email FROM users WHERE id = ?',
      [userId]
    );
    return rows[0];
  } catch (error) {
    console.error('Database query error:', error);
    throw error;
  }
}

// Example execution
getUserProfile(42).then(user =&amp;gt; console.log('User retrieved:', user));
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-takeaway-2&quot;&gt;The Takeaway&lt;/h4&gt;
&lt;p&gt;Connection pooling is a quiet champion of system architecture, transforming database communication from a slow bottleneck into a reliable, high-performance system utility. By setting up a robust pool inside your application, you prevent database crashes, lower response times, and ensure that your backend can handle high-concurrency traffic without breaking a sweat.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/09/supercharge-your-api-performance-with.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27378/stop-crashing-your-database-a-developers-guide-to-connection-pooling</guid>
<pubDate>Wed, 16 Sep 2026 08:51:01 +0000</pubDate>
</item>
<item>
<title>Core Web Vitals Benchmarks for Shopify Stores (2026 Data)</title>
<link>https://coderlegion.com/27086/core-web-vitals-benchmarks-for-shopify-stores-2026-data</link>
<description>&lt;p&gt;Shopify's own theme performance table still shows median pass rates in the 80–97% range for LCP, INP, CLS, and “all Core Web Vitals.” Open a multi-page Lighthouse sample on a real storefront and the story often flips: mobile Performance scores cluster near 50, lab LCP averages stretch well past Google's 2.5 s “good” line, and unused JavaScript shows up on almost every domain. Both views can be true. Theme-level CrUX aggregates describe shops that look like a given theme under field conditions. Portfolio monitoring needs URL-level and multi-page evidence, because apps, media, and collection templates decide what shoppers feel.&lt;/p&gt;&lt;p&gt;What follows separates those clocks, publishes a reproducible sample from Shopify leads we analyse in Apogee Watcher, and points at the tuning work that actually moves scores. For store-level fixes after you read the numbers, pair this with &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/shopify-speed-optimization-core-web-vitals-ecommerce&quot;&gt;Shopify Speed Optimization: Core Web Vitals Guide for E-Commerce&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;what-counts-as-a-reliable-shopify-core-web-vitals-benchmark-in-2026&quot; class=&quot;scroll-mt-24&quot;&gt;What counts as a reliable Shopify Core Web Vitals benchmark in 2026&lt;/h2&gt;&lt;p&gt;For Core Web Vitals, the strongest public field source remains the Chrome UX Report (CrUX). Google's methodology is clear about inclusion: pages and origins must be publicly discoverable and popular enough, data comes from eligible Chrome users, and thresholds are read at the 75th percentile over a rolling window. That is why Search Console and the field section of PageSpeed Insights matter for ranking and real-user claims, while a single Lighthouse run remains a diagnostic tool.&lt;/p&gt;&lt;p&gt;Google's published thresholds are unchanged for the three metrics:&lt;/p&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th&gt;Good (p75)&lt;/th&gt;&lt;th&gt;Needs improvement&lt;/th&gt;&lt;th&gt;Poor&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;LCP&lt;/td&gt;&lt;td&gt;≤ 2.5 s&lt;/td&gt;&lt;td&gt;2.5 s – 4.0 s&lt;/td&gt;&lt;td&gt;&amp;gt; 4.0 s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;INP&lt;/td&gt;&lt;td&gt;≤ 200 ms&lt;/td&gt;&lt;td&gt;200 ms – 500 ms&lt;/td&gt;&lt;td&gt;&amp;gt; 500 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CLS&lt;/td&gt;&lt;td&gt;≤ 0.1&lt;/td&gt;&lt;td&gt;0.1 – 0.25&lt;/td&gt;&lt;td&gt;&amp;gt; 0.25&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;An origin or URL group passes Core Web Vitals when all three sit in the good bucket at p75. A Lighthouse Performance score is a different artefact: it blends lab timings under a throttled environment and is useful for regression detection, not as a substitute for CrUX pass/fail. If you need a refresher on the three metrics themselves, start with &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/what-are-core-web-vitals-a-practical-guide-for-2026&quot;&gt;What Are Core Web Vitals? A Practical Guide for 2026&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;A useful Shopify benchmark therefore states four things up front: which clock (CrUX field versus Lighthouse lab), which URLs (homepage only versus collections, PDPs, cart), how scores are aggregated (single URL versus median or mean across pages), and the collection window. Platform marketing often quotes theme or “average store” figures without that scaffolding. Agencies managing twenty Shopify clients need the scaffolding more than another green screenshot.&lt;/p&gt;&lt;p&gt;Sources:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.chrome.com/docs/crux/methodology&quot;&gt;CrUX methodology&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.google.com/search/docs/appearance/core-web-vitals&quot;&gt;Understanding Core Web Vitals and Google search results&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://performance.shopify.com/blogs/blog/what-is-crux-and-how-to-use-it&quot;&gt;Shopify on using CrUX for real user performance&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;shopify-theme-level-crux-context-from-public-data&quot; class=&quot;scroll-mt-24&quot;&gt;Shopify theme-level CrUX context from public data&lt;/h2&gt;&lt;p&gt;Shopify's Performance team publishes aggregated Core Web Vitals performance by theme so merchants can compare popular themes under real Chrome traffic rather than a single lab screenshot. As of the 18 Aug 2026 update on their theme table, the median theme-level pass rates across listed themes were:&lt;/p&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th&gt;Median % of shops passing (theme table)&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Largest Contentful Paint (LCP)&lt;/td&gt;&lt;td&gt;90.3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cumulative Layout Shift (CLS)&lt;/td&gt;&lt;td&gt;93.4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Interaction to Next Paint (INP)&lt;/td&gt;&lt;td&gt;97.1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Views passing all CWVs&lt;/td&gt;&lt;td&gt;82.0%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Those figures combine devices, cover recent theme versions with enough eligible shops, and still describe theme-level cohorts, not “every live Shopify store with every app enabled.” The same table shows wide floors and ceilings: all-CWV pass rates range from roughly 37% to 95% depending on the theme row, which is the first warning that platform averages do not replace store-specific monitoring.&lt;/p&gt;&lt;p&gt;Third-party research often cites a lower all-CWV pass rate for Shopify as a whole (for example ~45% in vendor CrUX summaries). The gap between ~45% store-wide claims and ~82% median theme-level “all CWVs” is not a mystery if samples, devices, and theme filters differ, so treat both as orientation and do not merge them into one scorecard column.&lt;/p&gt;&lt;p&gt;Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://performance.shopify.com/pages/theme-performance-data-table&quot;&gt;Aggregated Core Web Vitals performance by Shopify Theme&lt;/a&gt;&lt;/p&gt;&lt;h2 id=&quot;multi-page-lab-sample-from-watchers-shopify-users&quot; class=&quot;scroll-mt-24&quot;&gt;Multi-page lab sample from Watcher's Shopify users&lt;/h2&gt;&lt;p&gt;We keep Shopify-tagged storefronts in Watcher and run multi-page PageSpeed Insights analyses that store both Lighthouse lab metrics and CrUX field payloads when Google returns them. For this snapshot we took the latest domain report per Shopify-tagged lead with an aggregated payload (n = 64 stores; reports generated between April and September 2026). Each report averages Lighthouse metrics across the URLs selected for that domain (median 17 analysed pages per store; 1,070 page runs in total). Values below are store-level averages of lab runs, not single-URL CrUX p75s.&lt;/p&gt;&lt;h3&gt;Mobile and desktop Lighthouse Performance&lt;/h3&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Slice&lt;/th&gt;&lt;th&gt;Mobile Performance (avg)&lt;/th&gt;&lt;th&gt;Desktop Performance (avg)&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Minimum&lt;/td&gt;&lt;td&gt;26&lt;/td&gt;&lt;td&gt;35&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;25th percentile&lt;/td&gt;&lt;td&gt;37&lt;/td&gt;&lt;td&gt;57&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Median&lt;/td&gt;&lt;td&gt;51&lt;/td&gt;&lt;td&gt;69&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;75th percentile&lt;/td&gt;&lt;td&gt;62&lt;/td&gt;&lt;td&gt;83&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Maximum&lt;/td&gt;&lt;td&gt;86&lt;/td&gt;&lt;td&gt;99&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Mean&lt;/td&gt;&lt;td&gt;51&lt;/td&gt;&lt;td&gt;69&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Half of the sample sits at or below a mobile Performance average of about 51. Only 32 of 64 stores clear a mobile average of 50, and 9 of 64 clear 70; none of the 64 store averages reached 90 on mobile. Desktop is kinder in the same runs (median about 69), which matches what agencies see when a client forwards a desktop-only screenshot and assumes the site is fine.&lt;/p&gt;&lt;h3&gt;Mobile lab Core Web Vitals timings (store averages)&lt;/h3&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th&gt;Median&lt;/th&gt;&lt;th&gt;75th percentile&lt;/th&gt;&lt;th&gt;Notes in this sample&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;LCP (s)&lt;/td&gt;&lt;td&gt;9.8&lt;/td&gt;&lt;td&gt;14.5&lt;/td&gt;&lt;td&gt;0 / 64 store averages ≤ 2.5 s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;INP (s)&lt;/td&gt;&lt;td&gt;0.30&lt;/td&gt;&lt;td&gt;0.41&lt;/td&gt;&lt;td&gt;15 / 64 store averages ≤ 0.20 s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CLS&lt;/td&gt;&lt;td&gt;0.02&lt;/td&gt;&lt;td&gt;0.07&lt;/td&gt;&lt;td&gt;55 / 64 store averages ≤ 0.1&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;CLS is the bright spot: layout stability is often already in a good lab band even when LCP is not. LCP is the structural problem. Multi-page mobile averages in the 8–15 s range do not mean every URL fails CrUX at that severity, but they do mean Lighthouse is consistently flagging heavy LCP candidates across templates. That is the signal agencies should take into a remediation backlog before arguing about a two-point Performance score change.&lt;/p&gt;&lt;h3&gt;CrUX origin categories when field data exists&lt;/h3&gt;&lt;p&gt;Not every lead has origin-level CrUX on the report. Where mobile origin overall category was present (n = 27), we saw 14 FAST, 8 AVERAGE, and 5 SLOW. Origin LCP was FAST on 22 of those stores, INP FAST on 22, and CLS FAST on 23. Field data is often healthier than multi-page lab averages on the same domains, especially for INP and CLS, because lab throttling and averaging across many templates punish media-heavy PDPs that may still pass origin-level CrUX when popular URLs are lighter.&lt;/p&gt;&lt;h3&gt;Recurring Lighthouse opportunities&lt;/h3&gt;&lt;p&gt;Across the 64 latest reports, the most common mobile opportunities were:&lt;/p&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Opportunity&lt;/th&gt;&lt;th&gt;Stores where it appeared&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Reduce unused JavaScript&lt;/td&gt;&lt;td&gt;61 / 64&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Reduce unused CSS&lt;/td&gt;&lt;td&gt;52 / 64&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Avoid multiple page redirects&lt;/td&gt;&lt;td&gt;26 / 64&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Minify JavaScript&lt;/td&gt;&lt;td&gt;17 / 64&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Minify CSS&lt;/td&gt;&lt;td&gt;10 / 64&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Unused JavaScript is effectively the default Shopify finding in this sample. Theme code, app scripts, analytics, and personalisation layers compete for the same main thread that INP and Total Blocking Time care about. For ecommerce metric priorities beyond the three Core Web Vitals, see &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/ecommerce-performance-monitoring-what-metrics-matter&quot;&gt;Performance Monitoring for E-Commerce: What Metrics Matter Most&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;why-shopify-core-web-vitals-still-vary-store-to-store&quot; class=&quot;scroll-mt-24&quot;&gt;Why Shopify Core Web Vitals still vary store to store&lt;/h2&gt;&lt;p&gt;Two shops on the same theme can land in different buckets for reasons that never appear in a theme marketing page:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;App footprint. Review widgets, loyalty, chat, upsell drawers, and A/B tools add script weight on collection and PDP templates.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Media strategy. Hero video, large product galleries, and unprioritised LCP images dominate mobile LCP in lab runs.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Template mix. A clean homepage can hide a slow cart drawer or a script-heavy collection grid when you only test one URL.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Headless versus online store. Hydrogen or other custom storefronts change what source-level theme IDs reveal and how caching works.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Traffic shape and geography. CrUX inclusion and p75 values move with real visitor mix; lab runs do not.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Platform-level medians help set expectations in a roadmap review. They do not replace scheduled checks on the URLs that drive revenue. That is why we treat theme tables as context and multi-page samples as the operating view.&lt;/p&gt;&lt;h2 id=&quot;global-dtc-shopify-cohort-lab-and-crux&quot; class=&quot;scroll-mt-24&quot;&gt;Global DTC Shopify cohort (lab and CrUX)&lt;/h2&gt;&lt;p&gt;We also tracked a public high-visibility cohort tagged &lt;code&gt;shopify-benchmark&lt;/code&gt; in Watcher (Allbirds, SKIMS, Kylie Cosmetics, Fashion Nova, Brooklinen, ColourPop, Steve Madden, Glossier, Netflix Shop, Mattel Creations). Six of those domains had multi-page Watcher lab reports in the lead pull above. On 7 Sep 2026 we also pulled mobile origin Chrome UX Report field data for the cohort via PageSpeed Insights (&lt;code&gt;originLoadingExperience&lt;/code&gt;), so lab and field sit in separate tables rather than one mixed scorecard.&lt;/p&gt;&lt;h3&gt;Lab multi-page averages (Watcher reports)&lt;/h3&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Brand&lt;/th&gt;&lt;th&gt;Domain&lt;/th&gt;&lt;th&gt;Mobile Performance (avg)&lt;/th&gt;&lt;th&gt;Desktop Performance (avg)&lt;/th&gt;&lt;th&gt;Mobile LCP avg (s)&lt;/th&gt;&lt;th&gt;Mobile CLS avg&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Fashion Nova&lt;/td&gt;&lt;td&gt;fashionnova.com&lt;/td&gt;&lt;td&gt;37&lt;/td&gt;&lt;td&gt;60&lt;/td&gt;&lt;td&gt;8.6&lt;/td&gt;&lt;td&gt;0.05&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Allbirds&lt;/td&gt;&lt;td&gt;allbirds.com&lt;/td&gt;&lt;td&gt;34&lt;/td&gt;&lt;td&gt;53&lt;/td&gt;&lt;td&gt;15.6&lt;/td&gt;&lt;td&gt;0.02&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SKIMS&lt;/td&gt;&lt;td&gt;skims.com&lt;/td&gt;&lt;td&gt;33&lt;/td&gt;&lt;td&gt;52&lt;/td&gt;&lt;td&gt;9.1&lt;/td&gt;&lt;td&gt;0.04&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Brooklinen&lt;/td&gt;&lt;td&gt;brooklinen.com&lt;/td&gt;&lt;td&gt;29&lt;/td&gt;&lt;td&gt;45&lt;/td&gt;&lt;td&gt;31.1&lt;/td&gt;&lt;td&gt;0.15&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Kylie Cosmetics&lt;/td&gt;&lt;td&gt;kyliecosmetics.com&lt;/td&gt;&lt;td&gt;26&lt;/td&gt;&lt;td&gt;36&lt;/td&gt;&lt;td&gt;26.8&lt;/td&gt;&lt;td&gt;0.22&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Netflix Shop&lt;/td&gt;&lt;td&gt;netflix.shop&lt;/td&gt;&lt;td&gt;26&lt;/td&gt;&lt;td&gt;41&lt;/td&gt;&lt;td&gt;15.7&lt;/td&gt;&lt;td&gt;0.15&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;These lab averages punish product-heavy template sets under throttling. They are not claims that every shopper sees a Performance score in the mid-20s.&lt;/p&gt;&lt;h3&gt;Mobile CrUX at origin (PageSpeed Insights, Sep 2026)&lt;/h3&gt;&lt;p&gt;Origin metrics are Chrome UX Report p75 values for phone traffic across the origin. CLS is shown on the usual 0–1 scale (PSI returns hundredths). “Passes all CWVs” means LCP, INP, and CLS are all in the good (FAST) bucket at p75.&lt;/p&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Brand&lt;/th&gt;&lt;th&gt;Domain&lt;/th&gt;&lt;th&gt;Origin overall&lt;/th&gt;&lt;th&gt;LCP p75&lt;/th&gt;&lt;th&gt;INP p75&lt;/th&gt;&lt;th&gt;CLS p75&lt;/th&gt;&lt;th&gt;Passes all CWVs?&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Allbirds&lt;/td&gt;&lt;td&gt;allbirds.com&lt;/td&gt;&lt;td&gt;Good&lt;/td&gt;&lt;td&gt;1.49 s (Good)&lt;/td&gt;&lt;td&gt;176 ms (Good)&lt;/td&gt;&lt;td&gt;0.00 (Good)&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ColourPop&lt;/td&gt;&lt;td&gt;colourpop.com&lt;/td&gt;&lt;td&gt;Needs improvement&lt;/td&gt;&lt;td&gt;1.66 s (Good)&lt;/td&gt;&lt;td&gt;212 ms (Needs improvement)&lt;/td&gt;&lt;td&gt;0.15 (Needs improvement)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Kylie Cosmetics&lt;/td&gt;&lt;td&gt;kyliecosmetics.com&lt;/td&gt;&lt;td&gt;Needs improvement&lt;/td&gt;&lt;td&gt;2.02 s (Good)&lt;/td&gt;&lt;td&gt;319 ms (Needs improvement)&lt;/td&gt;&lt;td&gt;0.04 (Good)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Netflix Shop&lt;/td&gt;&lt;td&gt;netflix.shop&lt;/td&gt;&lt;td&gt;Needs improvement&lt;/td&gt;&lt;td&gt;2.06 s (Good)&lt;/td&gt;&lt;td&gt;221 ms (Needs improvement)&lt;/td&gt;&lt;td&gt;0.13 (Needs improvement)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Steve Madden&lt;/td&gt;&lt;td&gt;stevemadden.com&lt;/td&gt;&lt;td&gt;Needs improvement&lt;/td&gt;&lt;td&gt;2.09 s (Good)&lt;/td&gt;&lt;td&gt;245 ms (Needs improvement)&lt;/td&gt;&lt;td&gt;0.10 (Good)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Mattel Creations&lt;/td&gt;&lt;td&gt;creations.mattel.com&lt;/td&gt;&lt;td&gt;Needs improvement&lt;/td&gt;&lt;td&gt;2.10 s (Good)&lt;/td&gt;&lt;td&gt;314 ms (Needs improvement)&lt;/td&gt;&lt;td&gt;0.02 (Good)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Glossier&lt;/td&gt;&lt;td&gt;glossier.com&lt;/td&gt;&lt;td&gt;Needs improvement&lt;/td&gt;&lt;td&gt;2.17 s (Good)&lt;/td&gt;&lt;td&gt;239 ms (Needs improvement)&lt;/td&gt;&lt;td&gt;0.09 (Good)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Fashion Nova&lt;/td&gt;&lt;td&gt;fashionnova.com&lt;/td&gt;&lt;td&gt;Needs improvement&lt;/td&gt;&lt;td&gt;2.52 s (Needs improvement)&lt;/td&gt;&lt;td&gt;360 ms (Needs improvement)&lt;/td&gt;&lt;td&gt;0.20 (Needs improvement)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SKIMS&lt;/td&gt;&lt;td&gt;skims.com&lt;/td&gt;&lt;td&gt;Poor&lt;/td&gt;&lt;td&gt;2.64 s (Needs improvement)&lt;/td&gt;&lt;td&gt;204 ms (Needs improvement)&lt;/td&gt;&lt;td&gt;0.32 (Poor)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Brooklinen&lt;/td&gt;&lt;td&gt;brooklinen.com&lt;/td&gt;&lt;td&gt;Needs improvement&lt;/td&gt;&lt;td&gt;3.49 s (Needs improvement)&lt;/td&gt;&lt;td&gt;287 ms (Needs improvement)&lt;/td&gt;&lt;td&gt;0.13 (Needs improvement)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Homepage URL-level field rows can differ from origin (for example Fashion Nova’s homepage LCP was Good while origin LCP was Needs improvement), so use origin for brand-level scorecards and URL groups for template work.&lt;/p&gt;&lt;p&gt;Allbirds is the clearest split in this cohort: Watcher multi-page lab Performance averaged about 34, with lab LCP around 16 s, while mobile origin CrUX passed all three Core Web Vitals. Brand scale does not guarantee a field pass either, because only Allbirds cleared all three origin metrics in this snapshot and INP (and sometimes CLS) kept most of the other famous stores out of the good bucket even when LCP looked acceptable. If a client asks “are we slower than the big Shopify brands?”, compare their CrUX origin or Search Console URL groups to this table, and keep Lighthouse for regression hunting on the same money-path templates.&lt;/p&gt;&lt;p&gt;At the other end of our wider Watcher sample, smaller or carefully trimmed storefronts can clear mobile lab averages in the mid-70s to mid-80s (for example Beauty Connections, Valmont Wear, and Bäckerei Matzker in this pull). Those shops still run on Shopify. The gap versus the famous lab cohort is mostly implementation load: fewer competing scripts, lighter heroes, and fewer template surprises across the multi-page set.&lt;/p&gt;&lt;h2 id=&quot;lab-lighthouse-scores-versus-crux-field-data-on-shopify&quot; class=&quot;scroll-mt-24&quot;&gt;Lab Lighthouse scores versus CrUX field data on Shopify&lt;/h2&gt;&lt;p&gt;Use both clocks without mixing them in one column:&lt;/p&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Question&lt;/th&gt;&lt;th&gt;Prefer&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Does Google’s page experience view look healthy?&lt;/td&gt;&lt;td&gt;CrUX / Search Console / PSI field (p75)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Did last week’s theme or app change regress templates?&lt;/td&gt;&lt;td&gt;Scheduled Lighthouse (lab), same URL set&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Are we comparing to Shopify’s theme table?&lt;/td&gt;&lt;td&gt;Theme CrUX aggregates (device mix, theme filter)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Are we comparing agency client A to client B?&lt;/td&gt;&lt;td&gt;Same tool, same URL roles, same aggregation&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Watcher domain reports already separate lab score cards from field methodology notes (median of URL-level p75s where present, origin badge when available). Keep that separation in client decks. A green origin badge with a mobile Performance average of 40 is not a contradiction; it is a prompt to inspect which templates the lab average is punishing. For portfolio monitoring design, see &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/core-web-vitals-monitoring-checklist-for-agencies&quot;&gt;Core Web Vitals Monitoring Checklist for Agencies&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/how-to-set-up-automated-pagespeed-monitoring&quot;&gt;How to Set Up Automated PageSpeed Monitoring&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;what-to-fix-first-when-shopify-lighthouse-opportunities-repeat&quot; class=&quot;scroll-mt-24&quot;&gt;What to fix first when Shopify Lighthouse opportunities repeat&lt;/h2&gt;&lt;p&gt;When unused JavaScript appears on nearly every store, a useful remediation order is usually:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Measure the money paths: homepage, top collections, top PDPs, cart, and checkout-adjacent templates, not a single marketing URL.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Cut or defer app scripts that are not required for first paint or first interaction on those templates.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Fix LCP candidates explicitly (image dimensions, priority hints, hero media weight) before chasing a Performance score vanity target.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Re-check CLS when banners, consent UI, or late-injected reviews shift layout after load.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Re-run the same URL set on a schedule so you can tell a real regression from run noise.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The detailed Shopify playbook for those steps lives in &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/shopify-speed-optimization-core-web-vitals-ecommerce&quot;&gt;Shopify Speed Optimization: Core Web Vitals Guide for E-Commerce&lt;/a&gt;. Benchmark numbers stay honest when prioritisation stops here and the how-to lives in that companion post.&lt;/p&gt;&lt;h2 id=&quot;how-to-run-a-repeatable-shopify-cwv-cohort-each-month&quot; class=&quot;scroll-mt-24&quot;&gt;How to run a repeatable Shopify CWV cohort each month&lt;/h2&gt;&lt;p&gt;A fixed process keeps each monthly refresh comparable:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Scope: mobile-first; LCP, INP, CLS; optional Lighthouse Performance as a lab companion metric.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;URL roles: same template set each month (home, collection, PDP, cart at minimum).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Aggregation: decide in advance whether you report origin CrUX, URL-level field, or multi-page lab averages, and never mix them in one table column.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Guardrails: mark insufficient CrUX explicitly; record tool, date, and page count.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Reporting: publish median cohort values, share of stores passing field CWV when available, top gains and regressions, and hypotheses (apps, media, theme) labelled as hypotheses.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;You can start wi
&lt;/p&gt;</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27086/core-web-vitals-benchmarks-for-shopify-stores-2026-data</guid>
<pubDate>Wed, 16 Sep 2026 08:04:00 +0000</pubDate>
</item>
<item>
<title>Core Web Vitals in 2026: What INP Means for Your Shopify and WordPress Rankings</title>
<link>https://coderlegion.com/27365/core-web-vitals-in-2026-what-inp-means-for-your-shopify-and-wordpress-rankings</link>
<description>&lt;blockquote&gt;&lt;p&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://shehrozaftab.com/blog/core-web-vitals-inp-2026&quot;&gt;shehrozaftab.com&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Google's Core Web Vitals are still part of how it judges page experience, and the biggest recent change catches out a lot of stores: &lt;strong&gt;INP replaced FID&lt;/strong&gt;. If your Shopify or WordPress site felt fine on the old metric but now shows warnings, this is usually why.&lt;/p&gt;
&lt;h3 id=&quot;the-three-core-web-vitals-in-2026&quot;&gt;The three Core Web Vitals in 2026&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LCP (Largest Contentful Paint):&lt;/strong&gt; how fast the main content loads. Good is 2.5s or less.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;INP (Interaction to Next Paint):&lt;/strong&gt; how quickly the page responds to taps and clicks across the whole visit. Good is 200ms or less.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLS (Cumulative Layout Shift):&lt;/strong&gt; how much the layout jumps around. Good is 0.1 or less.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-inp-is-stricter-than-the-old-fid&quot;&gt;Why INP is stricter than the old FID&lt;/h3&gt;
&lt;p&gt;The retired metric, FID (First Input Delay), only measured the delay on your very first interaction, and only the input delay part. It was easy to pass even when a site felt laggy.&lt;/p&gt;
&lt;p&gt;INP is tougher on purpose. It looks at interactions &lt;strong&gt;throughout the whole visit&lt;/strong&gt;, not just the first, and it measures the full time until the page visually responds, not just the initial delay. So heavy JavaScript, bloated theme scripts and third-party tags that used to slide past FID now show up clearly.&lt;/p&gt;
&lt;h3 id=&quot;what-tends-to-hurt-inp&quot;&gt;What tends to hurt INP&lt;/h3&gt;
&lt;p&gt;On real Shopify and WordPress sites, the usual culprits are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Too much JavaScript running on the main thread&lt;/strong&gt;, especially from apps and plugins you installed and forgot.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Heavy third-party tags:&lt;/strong&gt; chat widgets, popups, analytics, ad and tracking scripts all competing for the browser.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Page builders and app blocks&lt;/strong&gt; loading scripts sitewide even on pages that do not use them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Large, unbroken tasks&lt;/strong&gt; that block the browser from responding while they run.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;how-i-get-sites-back-into-the-green&quot;&gt;How I get sites back into the green&lt;/h3&gt;
&lt;p&gt;The approach is the same on both platforms, tuned to each:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Audit what is loading.&lt;/strong&gt; Measure with real field data (Chrome UX Report / Search Console), not just lab tests, because INP is about real interactions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cut the JavaScript.&lt;/strong&gt; Remove unused apps and plugins, defer non-critical scripts, and stop loading things on pages that do not need them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tame third-party tags.&lt;/strong&gt; Load chat, popups and analytics after interaction or on idle, not on first paint.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fix LCP and CLS alongside it.&lt;/strong&gt; Optimise the hero image, reserve space for images and embeds, and use font-display: optional so text does not jump.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Re-measure in the field&lt;/strong&gt; over the following weeks, since Core Web Vitals use a rolling 28-day window of real users.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-takeaway-3&quot;&gt;The takeaway&lt;/h3&gt;
&lt;p&gt;INP rewards sites that stay responsive the whole time someone is using them, not just on the first tap. For most Shopify and WordPress stores the fix is less code, fewer competing scripts, and disciplined third-party tags. Get those right and page experience stops being a drag on your rankings.&lt;/p&gt;
&lt;p&gt;Seeing INP or Core Web Vitals warnings in Search Console? &lt;a rel=&quot;nofollow&quot; href=&quot;https://shehrozaftab.com/contact&quot;&gt;Send me your site&lt;/a&gt; and I'll audit it and lay out exactly what to fix.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27365/core-web-vitals-in-2026-what-inp-means-for-your-shopify-and-wordpress-rankings</guid>
<pubDate>Wed, 16 Sep 2026 07:42:43 +0000</pubDate>
</item>
<item>
<title>Using searchParams in a Next.js Page Silently Opts It Out of Static Rendering Too</title>
<link>https://coderlegion.com/27252/using-searchparams-in-a-next-js-page-silently-opts-it-out-of-static-rendering-too</link>
<description>&lt;p&gt;I wrote earlier about a buried &lt;code&gt;cookies()&lt;/code&gt; call silently forcing an entire page into dynamic rendering. &lt;code&gt;searchParams&lt;/code&gt; causes the exact same category of behavior, through a completely different, and in some ways more surprising, mechanism, since using it doesn't look like reading anything server-specific at all.&lt;/p&gt;
&lt;h3 id=&quot;why-this-one-is-extra-surprising&quot;&gt;Why This One Is Extra Surprising&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;cookies()&lt;/code&gt; and &lt;code&gt;headers()&lt;/code&gt; at least sound like they're reaching into something request-specific, so forcing dynamic rendering feels intuitive once you think about it. &lt;code&gt;searchParams&lt;/code&gt; just looks like reading a prop, a completely normal-looking destructure at the top of a page component. There's nothing about the syntax that signals &quot;this is a server-specific value tied to this exact request.&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/products/page.tsx
export default function ProductsPage({
  searchParams,
}: {
  searchParams: Promise&amp;lt;{ category?: string; page?: string }&amp;gt;;
}) {
  // reading searchParams here means this page can no longer be
  // statically generated, full stop, regardless of what else it does
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The moment a page component accepts and uses &lt;code&gt;searchParams&lt;/code&gt;, Next.js treats the entire page as dynamic. This makes complete sense once you think through why, the query string is part of the actual request URL, and a page reading it necessarily produces different output for &lt;code&gt;/products?category=shoes&lt;/code&gt; versus &lt;code&gt;/products?category=hats&lt;/code&gt;, which is inherently incompatible with generating one fixed, static HTML output ahead of time. It's correct, intentional behavior. It's just not obviously signaled by the code itself the way reading &lt;code&gt;cookies()&lt;/code&gt; is.&lt;/p&gt;
&lt;h3 id=&quot;where-this-actually-costs-you&quot;&gt;Where This Actually Costs You&lt;/h3&gt;
&lt;p&gt;For a genuinely filterable page, a product listing with category and sort filters, this dynamic behavior is exactly correct and necessary, the whole feature depends on rendering different content per query string. This isn't a bug to fix in that case.&lt;/p&gt;
&lt;p&gt;The actual cost shows up when &lt;code&gt;searchParams&lt;/code&gt; gets accepted by a page component out of habit, or copied from a similar page, without the page actually using the specific query params in any way that changes its output.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/about/page.tsx
export default function AboutPage({
  searchParams, // accepted but never actually used for anything meaningful
}: {
  searchParams: Promise&amp;lt;{ [key: string]: string | undefined }&amp;gt;;
}) {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;About Us&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;We build things.&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If this component's type signature includes &lt;code&gt;searchParams&lt;/code&gt; but the actual rendered output never varies based on it, simply accepting the prop can still be enough to affect how Next.js treats the route, depending on whether the value is actually read and used within the render. A genuinely static about page, forced into unnecessary dynamic rendering because of a leftover parameter or copy-pasted type signature from a different page, loses real caching benefit for no actual functional reason.&lt;/p&gt;
&lt;h3 id=&quot;how-to-tell-if-this-is-actually-happening&quot;&gt;How to Tell If This Is Actually Happening&lt;/h3&gt;
&lt;p&gt;Same check as the earlier &lt;code&gt;cookies()&lt;/code&gt; post, run a real build and look at the route summary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A route marked dynamic (ƒ) that you'd expect to be static (○) is worth investigating. If the page genuinely needs to vary based on query parameters, that's correct and expected. If it doesn't, actually use those params to produce different output, the fix is simply not accepting or reading &lt;code&gt;searchParams&lt;/code&gt; in that component at all.&lt;/p&gt;
&lt;h3 id=&quot;the-distinction-that-actually-matters&quot;&gt;The Distinction That Actually Matters&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Does this specific page's rendered output genuinely need to differ based on the query string?&lt;/strong&gt; If yes, dynamic rendering triggered by &lt;code&gt;searchParams&lt;/code&gt; is correct, intentional, and not something to fight against, the feature depends on it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is &lt;code&gt;searchParams&lt;/code&gt; present in the component's signature or destructured, but never actually used to change what gets rendered?&lt;/strong&gt; That's the case worth fixing, either by removing the unused parameter entirely, or, if the params are genuinely needed for something that doesn't affect the main rendered content, handling that specific piece in a way that doesn't force the whole page dynamic, a client-side read via &lt;code&gt;useSearchParams()&lt;/code&gt; inside a small Client Component, for something that only needs to react to params after the initial static render.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// A pattern that keeps the main page static while still reacting to query params
// for a small, specific piece of UI that genuinely needs them
export default function ProductsPage() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Our Products&amp;lt;/h1&amp;gt;
      &amp;lt;ProductGrid /&amp;gt; {/* genuinely static content */}
      &amp;lt;QueryAwareBanner /&amp;gt; {/* small client component reading useSearchParams() */}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-broader-pattern&quot;&gt;The Broader Pattern&lt;/h3&gt;
&lt;p&gt;This is the same underlying lesson as the &lt;code&gt;cookies()&lt;/code&gt; post, applied to a different, less obviously &quot;server-specific&quot; mechanism. Next.js's static-versus-dynamic decision is driven by what a page actually reads and depends on, not by how obviously server-related that code looks at a glance. &lt;code&gt;searchParams&lt;/code&gt;, &lt;code&gt;cookies()&lt;/code&gt;, and &lt;code&gt;headers()&lt;/code&gt; all trigger the same category of behavior, and all three are easy to include without actually needing what they provide, especially when copying a page's structure from a similar one that genuinely did need it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Go check your own build output for any page accepting &lt;code&gt;searchParams&lt;/code&gt; that doesn't actually use it to change what gets rendered. If you find one, that's a free, easy static-rendering win sitting right there. Drop what you find in the comments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27252/using-searchparams-in-a-next-js-page-silently-opts-it-out-of-static-rendering-too</guid>
<pubDate>Tue, 15 Sep 2026 09:33:11 +0000</pubDate>
</item>
<item>
<title>Stop Overloading Your App: A Guide to Debouncing</title>
<link>https://coderlegion.com/27248/stop-overloading-your-app-a-guide-to-debouncing</link>
<description>&lt;h3 id=&quot;what-is-debouncing-&quot;&gt;What is Debouncing?&lt;/h3&gt;
&lt;p&gt;Debouncing is a programming practice used to ensure that a function only executes after a certain amount of time has passed since it was last triggered. In essence, it bundles a sequence of rapid-fire calls into a single execution.&lt;/p&gt;
&lt;h4 id=&quot;the-elevator-analogy&quot;&gt;The Elevator Analogy&lt;/h4&gt;
&lt;p&gt;Imagine you are standing in an elevator. As the doors begin to close, someone rushes up and hits the 'Open' button. The door reopens, and the timer to close the door resets. If people keep arriving and hitting that button, the elevator will never leave. The elevator only moves when there is a consistent, quiet pause of, say, 5 seconds without anyone pressing the button. Debouncing works the same way: it waits for the &quot;noise&quot; of rapid events to stop before finally firing your logic.&lt;/p&gt;
&lt;h4 id=&quot;why-it-matters-1&quot;&gt;Why It Matters&lt;/h4&gt;
&lt;p&gt;In modern web development, we often attach functions to events like &lt;code&gt;onScroll&lt;/code&gt;, &lt;code&gt;onResize&lt;/code&gt;, or &lt;code&gt;onKeyPress&lt;/code&gt;. If a user types into a search bar, an API request might fire for every single keystroke. Without debouncing, you might send 10 unnecessary requests to your database just because a user typed &quot;JavaScript.&quot; Debouncing saves server resources, reduces database load, and creates a smoother experience for the user.&lt;/p&gt;
&lt;h4 id=&quot;implementation-in-react&quot;&gt;Implementation in React&lt;/h4&gt;
&lt;p&gt;Here is how you might implement a debounced search input in a React component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import React, { useState, useEffect } from 'react';

const SearchBar = () =&amp;gt; {
  const [query, setQuery] = useState('');

  useEffect(() =&amp;gt; {
    const handler = setTimeout(() =&amp;gt; {
      console.log('Fetching results for:', query);
      // Imagine an API call here
    }, 500);

    return () =&amp;gt; clearTimeout(handler);
  }, [query]);

  return &amp;lt;input onChange={(e) =&amp;gt; setQuery(e.target.value)} /&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;takeaway&quot;&gt;Takeaway&lt;/h4&gt;
&lt;p&gt;Debouncing isn't just about saving bytes; it is about respecting the user's input and your system's limits. By introducing a small delay, you transform chaotic, repetitive input streams into clean, intentional actions, ensuring your backend only works when it truly needs to.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/09/mastering-performance-understanding.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27248/stop-overloading-your-app-a-guide-to-debouncing</guid>
<pubDate>Tue, 15 Sep 2026 09:03:36 +0000</pubDate>
</item>
<item>
<title>How Farm.js compiles React components into direct DOM updates</title>
<link>https://coderlegion.com/27180/how-farm-js-compiles-react-components-into-direct-dom-updates</link>
<description>&lt;hr&gt;
&lt;p&gt;title: How Farm.js compiles React components into direct DOM updates&lt;br&gt;
published: true&lt;/p&gt;
&lt;h3 id=&quot;tags-react-javascript-webdev-performance&quot;&gt;tags: react, javascript, webdev, performance&lt;/h3&gt;
&lt;p&gt;I've been building &lt;a rel=&quot;nofollow&quot; href=&quot;https://farmjs.dev&quot;&gt;Farm.js&lt;/a&gt;, a full-stack framework with an experimental ahead-of-time compiler for React (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/farming-labs/farm.js&quot;&gt;repo on GitHub&lt;/a&gt;). The short version: when the compiler can prove at build time exactly which DOM nodes a piece of state touches, it skips React's render cycle entirely for that update and writes to the DOM directly. When it can't prove it, the component stays on normal React. No new mental model, no different component syntax.&lt;/p&gt;
&lt;p&gt;You can try everything in this article without installing anything. The playground runs the real framework in your browser via WebContainers: &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackblitz.com/github/farming-labs/farm.js/tree/main/examples/stackblitz?file=src%2Fapp%2Fpage.tsx&quot;&gt;open it on StackBlitz&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This post is a deep dive into how the compiler works, with honest benchmark numbers (including the case where it does nothing), and then a tour of the rest of the framework.&lt;/p&gt;
&lt;h3 id=&quot;the-idea&quot;&gt;The idea&lt;/h3&gt;
&lt;p&gt;Take the most ordinary component imaginable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { useState } from &quot;react&quot;;

export function Counter(props: { initial: number }) {
  const [count, setCount] = useState(props.initial);
  return (
    &amp;lt;button
      className={count &amp;gt; 0 ? &quot;active&quot; : &quot;idle&quot;}
      onClick={() =&amp;gt; setCount((value) =&amp;gt; value + 1)}
    &amp;gt;
      Count: {count}
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you click, React re-runs the function, builds a new element tree, diffs it against the old one, and commits the differences. For this component that machinery is pure overhead: there is exactly one host element, and &lt;code&gt;count&lt;/code&gt; can only ever affect two things, the text content and the &lt;code&gt;className&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Farm compiler analyzes the component at build time and, when the structure is provable, emits a definition like this instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;createCompiledComponentWithFeatures(
  {
    initialize: (props) =&amp;gt; [props.initial],
    render: (props, state) =&amp;gt; (
      &amp;lt;button className={state[0].get() &amp;gt; 0 ? &quot;active&quot; : &quot;idle&quot;}&amp;gt;
        Count: {state[0].get()}
      &amp;lt;/button&amp;gt;
    ),
    bindings: [
      {
        kind: &quot;text&quot;,
        path: [],
        dependencies: [0],
        read: (_props, state) =&amp;gt; [&quot;Count: &quot;, state[0].get()],
      },
      {
        kind: &quot;attribute&quot;,
        path: [],
        dependencies: [0],
        name: &quot;className&quot;,
        read: (_props, state) =&amp;gt; (state[0].get() &amp;gt; 0 ? &quot;active&quot; : &quot;idle&quot;),
      },
    ],
  },
  [],
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The interesting part is &lt;code&gt;bindings&lt;/code&gt;. Each binding says: this state cell feeds this text node or this attribute, here is how to compute the value. &lt;code&gt;render&lt;/code&gt; still exists and React still owns the first mount, so hydration, refs, and devtools all see a normal component. But after mount, calling the setter walks the dependency graph and applies the affected bindings straight to the DOM. No render, no reconciliation, no commit phase. There's a runtime test in the repo that renders the compiled and baseline versions side by side and asserts the compiled one updates with zero React renders.&lt;/p&gt;
&lt;p&gt;The second argument, the empty array, is the component's capability list. Components that need more machinery (keyed lists, conditional ranges, nested compiled components) get feature modules in that array, and modules that don't use a feature never import it, so the unused structural runtimes are tree-shaken out of the production bundle. The measured runtime premium for direct-bindings-only output is 73.6% smaller than shipping the complete runtime.&lt;/p&gt;
&lt;h3 id=&quot;the-compiler-refuses-to-guess&quot;&gt;The compiler refuses to guess&lt;/h3&gt;
&lt;p&gt;This is the part I care most about. The compiler is intentionally conservative: it only transforms a component when it can prove there's one stable host element tree and every supported state value maps to known text or attribute targets. Anything it can't prove falls back to the original React component. From the docs, things that always stay on React include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;unkeyed or index-keyed list rendering&lt;/li&gt;
&lt;li&gt;effects, or hooks other than the supported &lt;code&gt;useState&lt;/code&gt; shape&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ref&lt;/code&gt;, &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;, JSX spreads&lt;/li&gt;
&lt;li&gt;multiple or conditional returns&lt;/li&gt;
&lt;li&gt;props whose updates carry identity (objects, arrays, functions, elements)&lt;/li&gt;
&lt;li&gt;async components, setters called outside JSX event handlers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fallback is silent by default, but you can turn on diagnostics:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[react-compiler] KeyedList: dynamic child structures require React reconciliation; using React.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And &lt;code&gt;report: true&lt;/code&gt; writes &lt;code&gt;.farm/react-compiler.json&lt;/code&gt; with exactly which components compiled and which fell back, with reasons and counts. So &quot;is my hot component actually compiled&quot; is a question with a checkable answer, not a vibe. You can also opt any component out with a &lt;code&gt;&quot;use no compiler&quot;&lt;/code&gt; directive.&lt;/p&gt;
&lt;p&gt;Enabling it is one flag on the renderer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { defineConfig } from &quot;@farm.js/core&quot;;
import { react } from &quot;@farm.js/react&quot;;

export default defineConfig({
  renderer: react({
    experimental: {
      compiler: true,
    },
  }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full compiler docs, including the complete eligibility table, are at &lt;a rel=&quot;nofollow&quot; href=&quot;https://farmjs.dev/docs&quot;&gt;farmjs.dev/docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;numbers-including-the-boring-ones&quot;&gt;Numbers, including the boring ones&lt;/h3&gt;
&lt;p&gt;The benchmark is a js-framework-benchmark-style keyed table (create 1,000 rows, update every 10th label, advance selection, swap two rows, clear), measured on production builds driven by Playwright Chromium. Latency is click dispatch to the MutationObserver callback for the resulting DOM writes, so it isn't quantized to frame boundaries the way rAF timing is. The runner installs the published npm packages rather than linking the workspace, and it fails hard unless the compiler report proves the workload component actually compiled, so it can't silently degrade into baseline vs baseline.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;action &lt;/th&gt;&lt;th&gt;baseline p50 &lt;/th&gt;&lt;th&gt;compiled p50 &lt;/th&gt;&lt;th&gt;p50 speedup&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;create &lt;/td&gt;&lt;td&gt;2.70ms &lt;/td&gt;&lt;td&gt;2.30ms &lt;/td&gt;&lt;td&gt;1.17x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;update &lt;/td&gt;&lt;td&gt;0.70ms &lt;/td&gt;&lt;td&gt;0.70ms &lt;/td&gt;&lt;td&gt;1.00x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;select &lt;/td&gt;&lt;td&gt;0.60ms &lt;/td&gt;&lt;td&gt;0.50ms &lt;/td&gt;&lt;td&gt;1.20x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;swap &lt;/td&gt;&lt;td&gt;3.40ms &lt;/td&gt;&lt;td&gt;0.50ms &lt;/td&gt;&lt;td&gt;6.80x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;clear &lt;/td&gt;&lt;td&gt;3.80ms &lt;/td&gt;&lt;td&gt;0.70ms &lt;/td&gt;&lt;td&gt;5.43x&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;CPU work (script + style + layout) per full action cycle: baseline 18.71ms, compiled 9.05ms, about 2x less.&lt;/p&gt;
&lt;p&gt;Notice &lt;code&gt;update&lt;/code&gt; is 1.00x. Row label updates were already cheap in React, and the compiled path doesn't magically beat the DOM write itself. The wins come where reconciliation is the cost: swaps, clears, structural churn on keyed lists. Methodology, correctness controls, and raw per-sample data live in &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/farming-labs/farm.js/tree/main/benchmarks/compiler&quot;&gt;&lt;code&gt;benchmarks/compiler&lt;/code&gt;&lt;/a&gt; in the repo if you want to pick it apart or rerun it.&lt;/p&gt;
&lt;h3 id=&quot;the-rest-of-the-framework&quot;&gt;The rest of the framework&lt;/h3&gt;
&lt;p&gt;The compiler is the flashy part, but I've spent most of the time on everything around it. Farm is a full-stack framework: Vite dev server, file-based app directory routing, streaming SSR, and Nitro production output. Here's what I think is worth showing.&lt;/p&gt;
&lt;h4 id=&quot;routes-are-typed-end-to-end&quot;&gt;Routes are typed, end to end&lt;/h4&gt;
&lt;p&gt;The app directory looks like what you already know (&lt;code&gt;page.tsx&lt;/code&gt;, &lt;code&gt;layout.tsx&lt;/code&gt;, &lt;code&gt;loading.tsx&lt;/code&gt;, &lt;code&gt;error.tsx&lt;/code&gt;, dynamic &lt;code&gt;[param]&lt;/code&gt; and catch-all &lt;code&gt;[[...slug]]&lt;/code&gt; segments). The difference is that Farm generates route types from it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export type RoutePath =
  | &quot;/&quot;
  | &quot;/about&quot;
  | `/repos/${string}/${string}`
  | `/users/${string}`;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Link&lt;/code&gt; hrefs are checked against this union, so a typo'd internal link is a type error, and the types regenerate on dev start and whenever routes change.&lt;/p&gt;
&lt;h4 id=&quot;server-functions-with-schemas-not-just-use-server-&quot;&gt;Server functions with schemas, not just &quot;use server&quot;&lt;/h4&gt;
&lt;p&gt;Server actions take zod schemas for input and output, so the wire boundary is validated in both directions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { createServerFn } from &quot;@farm.js/core/server-fn&quot;;
import { z } from &quot;zod&quot;;

export const submitMessage = createServerFn({
  input: z.object({
    name: z.string().trim().min(1, &quot;Name is required&quot;),
    message: z.string().trim().min(1, &quot;Message is required&quot;),
  }),
  output: z.object({ success: z.literal(true), message: publicMessageSchema }),
  async handler({ input }) {
    // runs on the server, even when called from a client component
  },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the client, &lt;code&gt;useServerFn&lt;/code&gt; gives you &lt;code&gt;formAction&lt;/code&gt; for progressive-enhancement forms plus optimistic updates with automatic rollback:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const action = useServerFn(submitMessage, {
  optimistic({ formData }) {
    return { success: true as const, message: { id: -1, /* ... */ } };
  },
  rollbackOnError: true,
});

&amp;lt;form action={action.formAction}&amp;gt;...&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And server queries are the read-side sibling, with cache keys, stale times, and server-side invalidation that reaches into client caches:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const productQuery = createServerQuery({
  input: z.object({ id: z.string() }),
  key: ({ input }) =&amp;gt; [&quot;product&quot;, input.id],
  staleTime: &quot;30s&quot;,
  async handler({ input }) { /* ... */ },
});

// in a mutation:
invalidate([&quot;product&quot;, input.id]);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;integrations-are-typed-capabilities-not-sdk-glue&quot;&gt;Integrations are typed capabilities, not SDK glue&lt;/h4&gt;
&lt;p&gt;This is the piece I haven't seen elsewhere. An integration (Stripe, Supabase, WorkOS, Polar, Unkey, and so on) mounts as a typed capability in config:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const appIntegrations = {
  billing: stripe({
    products: stripeProducts,
    instance: stripeInstance,
    webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
    async onWebhook(event) { /* typed webhook events */ },
  }),
} as const;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then four lines give your whole app a typed client for every mounted integration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { createIntegrations } from &quot;@farm.js/core/client&quot;;
import type { AppIntegrations } from &quot;./integrations&quot;;

export const { api, apiClient } = createIntegrations&amp;lt;AppIntegrations&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From a client component, &lt;code&gt;await apiClient.billing.checkout({ body: { productId } })&lt;/code&gt; is fully typed. The same call from a server component stays in-process instead of making an HTTP round trip back into your own app. The vendor SDK instance never leaves server-only modules.&lt;/p&gt;
&lt;h4 id=&quot;rendering-modes-as-directives&quot;&gt;Rendering modes as directives&lt;/h4&gt;
&lt;p&gt;Instead of exporting config objects, a route can declare its rendering mode in one line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&quot;use ssg; 60&quot;;

export default function BlogPage() {
  return &amp;lt;main&amp;gt;Blog&amp;lt;/main&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's static generation with 60-second revalidation. &lt;code&gt;use dynamic&lt;/code&gt; and &lt;code&gt;use ppr; 60&lt;/code&gt; (partial prerendering with a static shell and streamed dynamic holes) work the same way. The classic &lt;code&gt;export const revalidate = 60&lt;/code&gt; style works too.&lt;/p&gt;
&lt;h4 id=&quot;hydration-islands-with-click-replay&quot;&gt;Hydration islands with click replay&lt;/h4&gt;
&lt;p&gt;Client components can defer their own hydration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&quot;use client&quot;;

export const island = &quot;interaction&quot;;

export function CopyButton({ value }: { value: string }) {
  return &amp;lt;button onClick={() =&amp;gt; navigator.clipboard.writeText(value)}&amp;gt;Copy&amp;lt;/button&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;interaction&lt;/code&gt; means the component ships zero hydration work until the first click, and Farm replays that click after hydration so the user's first interaction isn't swallowed. There are also &lt;code&gt;visible&lt;/code&gt; and &lt;code&gt;idle&lt;/code&gt; strategies, with &lt;code&gt;load&lt;/code&gt; as the compatibility default.&lt;/p&gt;
&lt;h4 id=&quot;small-things-that-add-up&quot;&gt;Small things that add up&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;URL state hooks&lt;/strong&gt;, nuqs-style: &lt;code&gt;useQueryState('page', asInteger.withDefault(1))&lt;/code&gt; with history and throttling control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chainable middleware&lt;/strong&gt;: &lt;code&gt;middleware().use(...).redirect('/old', '/new').when(ctx =&amp;gt; ctx.pathname.startsWith('/api'), ...)&lt;/code&gt; with matcher/exclude config, per-segment middleware supported.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage helpers&lt;/strong&gt; for KV across local files, SQLite, Postgres, MySQL, Redis, and MongoDB from one import, and a pluggable shared cache adapter (Redis-backed ISR/PPR cache for multi-instance deploys).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Typed API routes&lt;/strong&gt; with &lt;code&gt;createEndpoint&lt;/code&gt; + zod, and a generated RPC-style client for them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;farm preview&lt;/code&gt;&lt;/strong&gt;: a public tunnel URL for your running local dev server, no build or deploy involved.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;farm doctor&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;farm explain &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/strong&gt;: the second one tells you exactly how a URL maps to a route and which runtime serves it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A docs site in your app&lt;/strong&gt; with &lt;code&gt;docs: { enabled: true }&lt;/code&gt;: markdown under &lt;code&gt;src/app/docs&lt;/code&gt; becomes a searchable docs site with llms.txt, sitemap, and robots handling out of the box.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skew protection&lt;/strong&gt;: when a deploy happens while a user has a tab open, stale clients get a structured 409 with a &quot;refresh before trying again&quot; contract instead of mystery failures.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;not-just-react&quot;&gt;Not just React&lt;/h4&gt;
&lt;p&gt;The compiler is React-specific, but the framework isn't. Renderers are one line in config, and Preact, Solid, Vue, and Svelte are first-class, with native bindings for each (&lt;code&gt;useServerQuery&lt;/code&gt; in Solid and Vue, &lt;code&gt;createServerQuery&lt;/code&gt; in Svelte, and so on). Same routes, same server functions, same integrations. Deployment targets cover Vercel, Cloudflare, Netlify, and plain Node, with any other Nitro preset accepted as pass-through.&lt;/p&gt;
&lt;h3 id=&quot;try-it&quot;&gt;Try it&lt;/h3&gt;
&lt;p&gt;The fastest path is the browser playground: &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackblitz.com/github/farming-labs/farm.js/tree/main/examples/stackblitz?file=src%2Fapp%2Fpage.tsx&quot;&gt;StackBlitz&lt;/a&gt;. Locally:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pnpm create @farm.js/app@beta my-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything is beta (&lt;code&gt;0.1.0-beta.61&lt;/code&gt; as I write this) and the compiler is explicitly experimental and opt-in. The fallback behavior is the contract I'm most committed to: if the compiler can't prove a component is safe to transform, you get exactly the React you wrote.&lt;/p&gt;
&lt;p&gt;The website is &lt;a rel=&quot;nofollow&quot; href=&quot;https://farmjs.dev&quot;&gt;farmjs.dev&lt;/a&gt;, docs at &lt;a rel=&quot;nofollow&quot; href=&quot;https://farmjs.dev/docs&quot;&gt;farmjs.dev/docs&lt;/a&gt;, and the repo is &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/farming-labs/farm.js&quot;&gt;github.com/farming-labs/farm.js&lt;/a&gt;. I'd genuinely love feedback, especially skeptical feedback, on the compiler's eligibility model and the typed integration approach. If you break something in the playground, an issue with a repro makes my day.&lt;/p&gt;
&lt;p&gt;{% embed &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/farming-labs/farm.js&quot;&gt;https://github.com/farming-labs/farm.js&lt;/a&gt; %}&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27180/how-farm-js-compiles-react-components-into-direct-dom-updates</guid>
<pubDate>Mon, 14 Sep 2026 16:21:03 +0000</pubDate>
</item>
<item>
<title>Stop Overworking Your Code: A Guide to Debouncing</title>
<link>https://coderlegion.com/27137/stop-overworking-your-code-a-guide-to-debouncing</link>
<description>&lt;h4 id=&quot;what-is-debouncing--1&quot;&gt;What is Debouncing?&lt;/h4&gt;
&lt;p&gt;Debouncing is a programming practice that ensures a specific task is only executed after a set amount of time has passed since the last time it was triggered. Essentially, it waits for a &quot;pause&quot; in activity before performing an action, preventing a function from running too many times in quick succession.&lt;/p&gt;
&lt;h4 id=&quot;the-elevator-analogy-1&quot;&gt;The Elevator Analogy&lt;/h4&gt;
&lt;p&gt;Imagine you are standing in an elevator in a busy office building. As you step in, the door starts to close. Suddenly, a coworker runs up and taps the &quot;open door&quot; button. The door reopens, resetting the timer. This happens again and again—every time someone arrives, the door's automatic closing process is canceled and restarted. The elevator only actually begins its journey once there has been a lull in arrivals for, say, five seconds. That pause is your debounce window.&lt;/p&gt;
&lt;h4 id=&quot;why-it-matters-2&quot;&gt;Why It Matters&lt;/h4&gt;
&lt;p&gt;In modern web development, users perform actions like typing into a search bar or resizing a browser window. If your application sends a network request to a database every single time a user presses a key, you are going to overwhelm your server and waste bandwidth. By using debouncing, we ensure the search request only triggers once the user has finished typing, making the experience smoother and the infrastructure much more stable.&lt;/p&gt;
&lt;h4 id=&quot;code-example&quot;&gt;Code Example&lt;/h4&gt;
&lt;p&gt;Here is a simple JavaScript function that &quot;debounces&quot; an input event:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function debounce(func, delay) {
  let timer;
  return (...args) =&amp;gt; {
    clearTimeout(timer);
    timer = setTimeout(() =&amp;gt; func(...args), delay);
  };
}

// Usage: Only runs 500ms after the user stops typing
const search = debounce(() =&amp;gt; console.log(&quot;Fetching results...&quot;), 500);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;takeaway-1&quot;&gt;Takeaway&lt;/h4&gt;
&lt;p&gt;Debouncing is less about doing less work and more about doing work at the right time. By strategically choosing when to execute expensive operations, you bridge the gap between user intent and system performance, creating a highly responsive application that doesn't buckle under the weight of constant, redundant events.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;resources&quot;&gt;Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Saurav-TB-Pandey/react-hook-lab&quot;&gt;react-hook-lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;react-hook-lab:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect with me on LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/pandeysaurav/&quot;&gt;Saurav Pandey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/09/why-your-search-bar-needs-debouncing-to.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27137/stop-overworking-your-code-a-guide-to-debouncing</guid>
<pubDate>Mon, 14 Sep 2026 09:21:17 +0000</pubDate>
</item>
<item>
<title>Redis Caching Strategies for High-Performance APIs</title>
<link>https://coderlegion.com/27011/redis-caching-strategies-for-high-performance-apis</link>
<description>&lt;h2 id=&quot;redis-caching-strategies-for-high-performance-apis&quot;&gt;Redis Caching Strategies for High-Performance APIs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The Cache That Made My API Faster—and Introduced a New Problem.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When I first started thinking seriously about API performance, my instinct was simple:&lt;/p&gt;
&lt;p&gt;If the database is slow, optimize the database.&lt;/p&gt;
&lt;p&gt;Add indexes. Improve the query. Tune the connection pool.&lt;/p&gt;
&lt;p&gt;And sometimes, that's exactly what you should do.&lt;/p&gt;
&lt;p&gt;But eventually, you run into a different problem.&lt;/p&gt;
&lt;p&gt;The database isn't necessarily slow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You're just asking it the same question too many times.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Imagine an endpoint that returns product information.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;GET /api/products/123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The product changes once every few hours, but the endpoint gets thousands of requests every minute.&lt;/p&gt;
&lt;p&gt;Without caching, every request goes straight to PostgreSQL:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Request
   ↓
API
   ↓
PostgreSQL
   ↓
Response
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The database is doing the same work over and over again.&lt;/p&gt;
&lt;p&gt;That's where Redis comes in.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-simple-idea-behind-caching&quot;&gt;The Simple Idea Behind Caching&lt;/h2&gt;
&lt;p&gt;Instead of asking the database every time, we store frequently requested data temporarily.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Request
   ↓
Redis
   ↓
  HIT ─────→ Response
   │
  MISS
   ↓
Database
   ↓
Redis
   ↓
Response
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first request pays the database cost.&lt;/p&gt;
&lt;p&gt;The next requests don't.&lt;/p&gt;
&lt;p&gt;In a Node.js API, a simple cache-aside implementation looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const key = `product:${productId}`;

const cached = await redis.get(key);

if (cached) {
  return JSON.parse(cached);
}

const product = await prisma.product.findUnique({
  where: { id: productId },
});

if (!product) {
  throw new Error(&quot;Product not found&quot;);
}

await redis.set(
  key,
  JSON.stringify(product),
  { EX: 300 }
);

return product;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's the basic pattern:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check Redis → cache miss → query database → store result → return.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Simple.&lt;/p&gt;
&lt;p&gt;But this is where caching stops being just a performance optimization.&lt;/p&gt;
&lt;p&gt;Because now we have two copies of the data.&lt;/p&gt;
&lt;p&gt;And they can disagree.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-stale-data-problem&quot;&gt;The Stale Data Problem&lt;/h2&gt;
&lt;p&gt;Suppose Redis contains:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;product:123
price: ₦45,000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then someone updates the product:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Database
price: ₦50,000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Redis still contains:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;₦45,000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the cache has a five-minute TTL, users could receive the old price for another five minutes.&lt;/p&gt;
&lt;p&gt;This is the real challenge with caching.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Making data fast to access is easy. Keeping it correct is harder.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's why TTL alone isn't enough.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;ttl-vs-invalidation&quot;&gt;TTL vs Invalidation&lt;/h2&gt;
&lt;p&gt;TTL answers:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;How long should this cache entry exist?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Invalidation answers:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&quot;When should I remove it because the underlying data changed?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;await prisma.product.update({
  where: { id: productId },
  data: updateData,
});

await redis.del(`product:${productId}`);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the next request becomes a cache miss and retrieves the latest value from the database.&lt;/p&gt;
&lt;p&gt;For many APIs, &lt;strong&gt;cache-aside + TTL + delete-on-write&lt;/strong&gt; is a very practical starting point.&lt;/p&gt;
&lt;p&gt;You don't need a complicated caching architecture on day one.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;then-there-s-the-cache-stampede&quot;&gt;Then There's the Cache Stampede&lt;/h2&gt;
&lt;p&gt;Here's where things get interesting.&lt;/p&gt;
&lt;p&gt;Imagine a popular cache entry expires:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;product:123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the exact same time, 1,000 requests arrive.&lt;/p&gt;
&lt;p&gt;They all check Redis:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;MISS
MISS
MISS
MISS
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And suddenly all 1,000 requests hit PostgreSQL.&lt;/p&gt;
&lt;p&gt;The cache that was supposed to protect the database has just created a database traffic spike.&lt;/p&gt;
&lt;p&gt;This is commonly called a &lt;strong&gt;cache stampede&lt;/strong&gt; or &lt;strong&gt;thundering herd&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For extremely hot keys, you can prevent this by allowing only one request to rebuild the cache while the others wait.&lt;/p&gt;
&lt;p&gt;Redis can help here through distributed locking.&lt;/p&gt;
&lt;p&gt;But I wouldn't introduce locking everywhere.&lt;/p&gt;
&lt;p&gt;If a query takes 10ms, the complexity may not be worth it.&lt;/p&gt;
&lt;p&gt;If rebuilding the cache takes several seconds under heavy traffic, it's a different conversation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimize for the workload you actually have.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-should-you-cache-&quot;&gt;What Should You Cache?&lt;/h2&gt;
&lt;p&gt;This is probably the most important question.&lt;/p&gt;
&lt;p&gt;I wouldn't cache something simply because Redis is available.&lt;/p&gt;
&lt;p&gt;Good candidates usually have three characteristics:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Frequently requested
+
Expensive to retrieve
+
Safe to be slightly stale
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Product details
Public profiles
Search results
Configuration
Reference data
Expensive aggregations
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'd be much more careful with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Account balances
Payment state
Authorization decisions
Inventory
Transaction state
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Especially in financial systems.&lt;/p&gt;
&lt;p&gt;If a wallet contains ₦100,000 and Redis says ₦100,000 while the database says ₦70,000 after a transaction, using that cached value for a financial decision is dangerous.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fast wrong data is still wrong data.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For critical state, the database should remain authoritative.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;your-cache-key-is-part-of-your-architecture&quot;&gt;Your Cache Key Is Part of Your Architecture&lt;/h2&gt;
&lt;p&gt;A cache key isn't just a random string.&lt;/p&gt;
&lt;p&gt;This:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is a terrible cache key.&lt;/p&gt;
&lt;p&gt;This is better:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;user:123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in a multi-tenant system:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tenant:456:user:123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For filtered results:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;products:category:phones:page:1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every parameter that changes the response needs to be represented appropriately in the key.&lt;/p&gt;
&lt;p&gt;Otherwise, you can end up returning the wrong cached response to the right request.&lt;/p&gt;
&lt;p&gt;That's a correctness bug—not a performance bug.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;redis-should-accelerate-your-system-not-become-your-system&quot;&gt;Redis Should Accelerate Your System, Not Become Your System&lt;/h2&gt;
&lt;p&gt;One of the most important design decisions is what happens when Redis fails.&lt;/p&gt;
&lt;p&gt;For a cache-aside architecture, I'd rather have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Redis available
    ↓
Use cache

Redis unavailable
    ↓
Use database
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The API might become slower.&lt;/p&gt;
&lt;p&gt;But it remains functional.&lt;/p&gt;
&lt;p&gt;That's a much better failure mode than:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Redis unavailable
    ↓
Entire API unavailable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your application completely depends on Redis just to retrieve data that already exists in PostgreSQL, you've accidentally turned your cache into a critical dependency.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;measure-the-difference&quot;&gt;Measure the Difference&lt;/h2&gt;
&lt;p&gt;Finally, don't add Redis and declare victory.&lt;/p&gt;
&lt;p&gt;Measure it.&lt;/p&gt;
&lt;p&gt;Before caching:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Average latency: 400ms
P95 latency:     800ms
DB queries/sec:  2,000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After caching:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Average latency: 70ms
P95 latency:     150ms
DB queries/sec:  500
Cache hit rate:  85%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you have evidence.&lt;/p&gt;
&lt;p&gt;The goal isn't to make Redis busy.&lt;/p&gt;
&lt;p&gt;The goal is to make the &lt;strong&gt;entire system do less unnecessary work.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-bigger-lesson&quot;&gt;The Bigger Lesson&lt;/h2&gt;
&lt;p&gt;Redis isn't what makes an API fast.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Avoiding unnecessary work makes an API fast.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Redis is simply one of the tools that helps you do that.&lt;/p&gt;
&lt;p&gt;The moment you introduce a cache, you're making a trade:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;More memory
+
More complexity
+
Potentially stale data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;in exchange for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Lower latency
+
Less database load
+
Higher throughput
+
Better scalability
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The best caching strategy isn't the one that puts the most data into Redis.&lt;/p&gt;
&lt;p&gt;It's the one that &lt;strong&gt;eliminates the most unnecessary work while keeping the system correct.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's the difference between simply &lt;strong&gt;using Redis&lt;/strong&gt; and actually &lt;strong&gt;designing with Redis&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27011/redis-caching-strategies-for-high-performance-apis</guid>
<pubDate>Sat, 12 Sep 2026 22:30:40 +0000</pubDate>
</item>
<item>
<title>GPU-Accelerating MSCRED with CUDA, im2col, GEMM, and a Custom PyTorch Extension</title>
<link>https://coderlegion.com/26883/gpu-accelerating-mscred-with-cuda-im2col-gemm-and-a-custom-pytorch-extension</link>
<description>&lt;h3 id=&quot;introduction-1&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;During my M.Tech in Data Science and Artificial Intelligence at IIT Bhilai (2021–2023), I conducted thesis research on multivariate time-series anomaly detection and GPU acceleration using industrial process signals.&lt;/p&gt;
&lt;p&gt;My thesis, titled &lt;strong&gt;“Anomaly Detection of Multivariate Time Series Data and Acceleration Using GPUs,”&lt;/strong&gt; explored an MSCRED—Multi-Scale Convolutional Recurrent Encoder-Decoder—and ConvLSTM-based pipeline for modelling relationships between signals and detecting abnormal temporal patterns.&lt;/p&gt;
&lt;p&gt;Although the model produced useful anomaly-detection results, its convolution-heavy encoder created a significant CPU bottleneck. I therefore implemented GPU-accelerated operations using CUDA, transformed convolution into an &lt;code&gt;im2col&lt;/code&gt; plus GEMM workflow, investigated global-memory and shared-memory implementations, and integrated the accelerated operations with PyTorch.&lt;/p&gt;
&lt;p&gt;In the measured workload, encoder runtime decreased from &lt;strong&gt;300 seconds on the CPU to 23.664 seconds on the GPU&lt;/strong&gt;, representing an approximately &lt;strong&gt;12.68× encoder speed-up&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Research attribution:&lt;/strong&gt; MSCRED and ConvLSTM are established architectures introduced by the researchers cited in the References section. This article describes how I used, adapted, profiled, and accelerated selected operations as part of my M.Tech thesis.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Data confidentiality:&lt;/strong&gt; The original research involved industrial process data. This article discusses the architecture, optimisation methodology, and aggregate benchmark results without publishing proprietary signals, internal process details, confidential datasets, or restricted source code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-anomaly-detection-problem&quot;&gt;The Anomaly-Detection Problem&lt;/h3&gt;
&lt;p&gt;Industrial systems often generate many related sensor and process-control signals at the same time. A single signal may appear normal when viewed independently, while the relationship between several signals may indicate an abnormal operating condition.&lt;/p&gt;
&lt;p&gt;This makes multivariate time-series anomaly detection more difficult than analysing one signal at a time.&lt;/p&gt;
&lt;p&gt;The model must learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Relationships between multiple signals&lt;/li&gt;
&lt;li&gt;Changes across different time windows&lt;/li&gt;
&lt;li&gt;Temporal dependencies between successive observations&lt;/li&gt;
&lt;li&gt;Deviations from normal operating patterns&lt;/li&gt;
&lt;li&gt;Reconstruction differences that may indicate anomalies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal of the research was to detect unusual multivariate patterns while preserving the temporal and spatial relationships present in the data.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;mscred-and-convlstm-architecture&quot;&gt;MSCRED and ConvLSTM Architecture&lt;/h3&gt;
&lt;p&gt;MSCRED models relationships between multiple time-series signals by constructing &lt;strong&gt;signature matrices&lt;/strong&gt; over different temporal windows.&lt;/p&gt;
&lt;p&gt;At a high level, the pipeline contains:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Signature-matrix generation from multivariate signals&lt;/li&gt;
&lt;li&gt;A convolutional encoder for spatial feature extraction&lt;/li&gt;
&lt;li&gt;ConvLSTM layers for temporal modelling&lt;/li&gt;
&lt;li&gt;A decoder that reconstructs expected signal relationships&lt;/li&gt;
&lt;li&gt;Reconstruction-error analysis for anomaly scoring&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The encoder processes the signature matrices through convolutional operations, while ConvLSTM captures how the encoded representations evolve over time.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Multivariate signals
        ↓
Signature matrices
        ↓
CNN encoder
        ↓
ConvLSTM
        ↓
Decoder
        ↓
Reconstruction error
        ↓
Anomaly score
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-signature-matrices-are-useful&quot;&gt;Why Signature Matrices Are Useful&lt;/h4&gt;
&lt;p&gt;A signature matrix represents pairwise relationships between signals over a selected time interval.&lt;/p&gt;
&lt;p&gt;By generating these matrices at multiple scales, the model can observe both short-term and longer-term relationships. This helps identify anomalies that may not be visible in an individual signal.&lt;/p&gt;
&lt;h4 id=&quot;role-of-the-encoder&quot;&gt;Role of the Encoder&lt;/h4&gt;
&lt;p&gt;The convolutional encoder extracts spatial patterns from the signature matrices. It reduces the raw matrix representation into compact feature maps that capture important relationships between signals.&lt;/p&gt;
&lt;h4 id=&quot;role-of-convlstm&quot;&gt;Role of ConvLSTM&lt;/h4&gt;
&lt;p&gt;ConvLSTM models how those spatial representations change over time.&lt;/p&gt;
&lt;p&gt;Unlike a standard fully connected LSTM, ConvLSTM preserves the spatial structure of feature maps while learning temporal dependencies.&lt;/p&gt;
&lt;h4 id=&quot;role-of-the-decoder&quot;&gt;Role of the Decoder&lt;/h4&gt;
&lt;p&gt;The decoder reconstructs the expected signature matrices.&lt;/p&gt;
&lt;p&gt;When the reconstructed output differs significantly from the observed input, the reconstruction error can indicate an anomalous pattern.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;anomaly-detection-evaluation&quot;&gt;Anomaly-Detection Evaluation&lt;/h3&gt;
&lt;p&gt;The purpose of the MSCRED and ConvLSTM pipeline was to detect abnormal relationships and temporal behaviour across multiple industrial process signals.&lt;/p&gt;
&lt;p&gt;The model learned normal multivariate patterns by reconstructing signature matrices. During evaluation, the difference between the observed signature matrices and the reconstructed outputs was used to calculate a reconstruction error.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Observed signature matrix
            ↓
MSCRED reconstruction
            ↓
Reconstruction error
            ↓
Thresholding
            ↓
Normal or anomalous
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A larger reconstruction error indicated that the observed relationships differed from patterns learned during normal operation and could therefore represent an anomaly.&lt;/p&gt;
&lt;p&gt;The thesis evaluated anomaly-detection quality using the F1 score. Under the reported experimental setup, the &lt;strong&gt;best recorded F1 score was 0.941&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This result represents model-quality evaluation and should be interpreted separately from the CUDA runtime measurements presented later. The GPU memory optimisations were designed to reduce computation time, not to improve predictive accuracy.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;where-the-performance-bottleneck-appeared&quot;&gt;Where the Performance Bottleneck Appeared&lt;/h3&gt;
&lt;p&gt;The encoder repeatedly applied convolutional operations to signature matrices.&lt;/p&gt;
&lt;p&gt;On the CPU, these operations involved substantial nested computation across:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Input channels&lt;/li&gt;
&lt;li&gt;Output filters&lt;/li&gt;
&lt;li&gt;Spatial positions&lt;/li&gt;
&lt;li&gt;Kernel dimensions&lt;/li&gt;
&lt;li&gt;Batch elements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This made the encoder a strong candidate for GPU acceleration because many convolution calculations could be performed in parallel.&lt;/p&gt;
&lt;p&gt;The objective was not to redesign the anomaly-detection model. Instead, it was to preserve its expected computational behaviour while accelerating selected expensive operations.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;transforming-conv2d-into-im2col-and-gemm&quot;&gt;Transforming Conv2D into &lt;code&gt;im2col&lt;/code&gt; and GEMM&lt;/h3&gt;
&lt;p&gt;A convolution repeatedly applies a kernel to overlapping regions of an input feature map.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;im2col&lt;/code&gt;, or image-to-column, transformation rearranges those local regions into columns of a matrix.&lt;/p&gt;
&lt;p&gt;After this transformation, convolution can be expressed as a general matrix multiplication:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Output matrix = Weight matrix × im2col(Input)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This conversion is useful because matrix multiplication is highly parallel and can be executed efficiently on a GPU.&lt;/p&gt;
&lt;h4 id=&quot;conceptual-transformation&quot;&gt;Conceptual Transformation&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Input feature map
        ↓
Extract overlapping kernel windows
        ↓
Arrange windows as matrix columns
        ↓
Multiply by reshaped filter matrix
        ↓
Reshape result into output feature map
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-use-gemm-&quot;&gt;Why Use GEMM?&lt;/h4&gt;
&lt;p&gt;GEMM stands for &lt;strong&gt;General Matrix-Matrix Multiplication&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Modern GPU architectures are designed to perform large numbers of multiply-and-accumulate operations in parallel. Expressing convolution as GEMM allows the workload to use that parallelism.&lt;/p&gt;
&lt;h4 id=&quot;trade-off&quot;&gt;Trade-Off&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;im2col&lt;/code&gt; introduces additional memory usage because overlapping input regions are copied into an intermediate matrix.&lt;/p&gt;
&lt;p&gt;The method is most effective when the computational benefit of parallel matrix multiplication outweighs the cost of creating and storing the intermediate representation.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;cuda-kernel-design&quot;&gt;CUDA Kernel Design&lt;/h3&gt;
&lt;p&gt;The CUDA implementation divided the computation across many GPU threads.&lt;/p&gt;
&lt;p&gt;Each thread was responsible for part of the output, while thread blocks grouped related work.&lt;/p&gt;
&lt;p&gt;Important kernel-design considerations included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mapping output elements to threads&lt;/li&gt;
&lt;li&gt;Selecting thread-block dimensions&lt;/li&gt;
&lt;li&gt;Coalescing memory access where possible&lt;/li&gt;
&lt;li&gt;Reducing unnecessary global-memory operations&lt;/li&gt;
&lt;li&gt;Avoiding excessive synchronisation&lt;/li&gt;
&lt;li&gt;Preserving correct tensor indexing&lt;/li&gt;
&lt;li&gt;Managing intermediate buffers efficiently&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The implementation required careful validation because incorrect indexing in GPU code can produce plausible-looking but incorrect outputs.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;global-memory-and-shared-memory&quot;&gt;Global Memory and Shared Memory&lt;/h3&gt;
&lt;p&gt;The first GPU implementation relied primarily on &lt;strong&gt;global memory&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Global memory provides access to the complete input, but its latency is considerably higher than that of on-chip shared memory.&lt;/p&gt;
&lt;p&gt;The optimised implementation staged reusable tiles inside &lt;strong&gt;shared memory&lt;/strong&gt;. Threads within the same block could then reuse nearby values without repeatedly loading them from global memory.&lt;/p&gt;
&lt;p&gt;The shared-memory optimisation was intended to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reduce repeated global-memory reads&lt;/li&gt;
&lt;li&gt;Improve data locality&lt;/li&gt;
&lt;li&gt;Increase data reuse within each thread block&lt;/li&gt;
&lt;li&gt;Reduce memory-related kernel overhead&lt;/li&gt;
&lt;li&gt;Improve matrix-multiplication efficiency&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;conceptual-data-flow&quot;&gt;Conceptual Data Flow&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Global memory
      ↓
Load reusable tile
      ↓
Shared memory
      ↓
Threads perform repeated calculations
      ↓
Write result to global memory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Shared memory is limited in capacity, so tile dimensions and thread-block organisation must be selected carefully.&lt;/p&gt;
&lt;p&gt;Poorly selected configurations can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reduce occupancy&lt;/li&gt;
&lt;li&gt;Increase synchronisation overhead&lt;/li&gt;
&lt;li&gt;Create bank conflicts&lt;/li&gt;
&lt;li&gt;Use too much shared memory per block&lt;/li&gt;
&lt;li&gt;Limit the number of active thread blocks&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;integrating-the-cuda-implementation-with-pytorch&quot;&gt;Integrating the CUDA Implementation with PyTorch&lt;/h3&gt;
&lt;p&gt;To use the accelerated operations inside the existing model pipeline, I connected the CUDA implementation to PyTorch through a custom extension.&lt;/p&gt;
&lt;p&gt;The integration enabled PyTorch tensors to be passed to the CUDA implementation and the computed results to be returned to the model pipeline.&lt;/p&gt;
&lt;p&gt;Important integration concerns included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tensor dimensions&lt;/li&gt;
&lt;li&gt;Device placement&lt;/li&gt;
&lt;li&gt;Data types&lt;/li&gt;
&lt;li&gt;Contiguous-memory assumptions&lt;/li&gt;
&lt;li&gt;Compilation and linking&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;li&gt;CPU-versus-GPU output comparison&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Correctness validation was essential. A faster kernel is not useful if it changes tensor shapes, produces incorrect indexing, introduces unacceptable numerical differences, or behaves inconsistently for the tested configurations.&lt;/p&gt;
&lt;p&gt;The custom extension made it possible to retain PyTorch for model development while using lower-level CUDA operations for selected performance-critical components.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;experimental-setup&quot;&gt;Experimental Setup&lt;/h3&gt;
&lt;p&gt;The benchmark used the following environment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GPU:&lt;/strong&gt; NVIDIA RTX A6000, 48 GB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU:&lt;/strong&gt; 16-core Intel Xeon Gold 5218&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System memory:&lt;/strong&gt; 64 GB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data points:&lt;/strong&gt; 36,000&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Number of features:&lt;/strong&gt; 30&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batch size:&lt;/strong&gt; 128&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU threads:&lt;/strong&gt; 32&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;timing-methodology&quot;&gt;Timing Methodology&lt;/h4&gt;
&lt;p&gt;The timings reported here are measurements from my thesis experiments using the specified hardware and workload. The CPU and GPU implementations used the same input dimensions and batch configuration.&lt;/p&gt;
&lt;p&gt;The results should be treated as comparative experimental measurements rather than universal library benchmarks. Performance may vary with implementation details, CUDA configuration, tensor dimensions, data-transfer costs, compiler settings, and hardware.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;performance-benchmark-results&quot;&gt;Performance Benchmark Results&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Component &lt;/th&gt;&lt;th&gt;CPU &lt;/th&gt;&lt;th&gt;GPU: global memory &lt;/th&gt;&lt;th&gt;GPU: shared memory&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Encoder &lt;/td&gt;&lt;td&gt;300 s &lt;/td&gt;&lt;td&gt;27.492 s &lt;/td&gt;&lt;td&gt;23.664 s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ConvLSTM &lt;/td&gt;&lt;td&gt;1,200 s &lt;/td&gt;&lt;td&gt;909.208 s &lt;/td&gt;&lt;td&gt;847.260 s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Total runtime &lt;/td&gt;&lt;td&gt;2,700 s &lt;/td&gt;&lt;td&gt;1,594 s &lt;/td&gt;&lt;td&gt;1,481 s&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;results-at-a-glance&quot;&gt;Results at a Glance&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Anomaly-detection quality:&lt;/strong&gt; best recorded F1 score of &lt;strong&gt;0.941&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Encoder acceleration:&lt;/strong&gt; approximately &lt;strong&gt;12.68×&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;End-to-end acceleration:&lt;/strong&gt; approximately &lt;strong&gt;1.82×&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total runtime reduction:&lt;/strong&gt; approximately &lt;strong&gt;45.1%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The shared-memory implementation reduced encoder runtime from &lt;strong&gt;300 seconds to 23.664 seconds&lt;/strong&gt;, producing an approximately &lt;strong&gt;12.68× encoder speed-up&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For the complete measured pipeline, runtime decreased from &lt;strong&gt;2,700 seconds to 1,481 seconds&lt;/strong&gt;. The total-runtime measurement represents the complete measured pipeline rather than only the encoder and ConvLSTM components shown separately.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-the-encoder-improved-more-than-convlstm&quot;&gt;Why the Encoder Improved More Than ConvLSTM&lt;/h3&gt;
&lt;p&gt;The convolutional encoder benefited strongly from GPU execution because its operations provided substantial data parallelism.&lt;/p&gt;
&lt;p&gt;ConvLSTM achieved a smaller improvement because of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Recurrent dependencies&lt;/li&gt;
&lt;li&gt;Sequential temporal processing&lt;/li&gt;
&lt;li&gt;Memory movement&lt;/li&gt;
&lt;li&gt;Repeated state updates&lt;/li&gt;
&lt;li&gt;More limited parallelism across time steps&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Consequently, the &lt;strong&gt;12.68× encoder acceleration&lt;/strong&gt; did not translate into a &lt;strong&gt;12.68× end-to-end speed-up&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;After the encoder was accelerated, ConvLSTM became the dominant runtime bottleneck.&lt;/p&gt;
&lt;p&gt;This is an important performance-engineering lesson: accelerating one component can move the bottleneck to another part of the system.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;correctness-validation&quot;&gt;Correctness Validation&lt;/h3&gt;
&lt;p&gt;The CUDA implementation was compared with the reference implementation for the tested tensor shapes and configurations.&lt;/p&gt;
&lt;p&gt;The optimisation work focused on reducing execution time while preserving the expected computational behaviour. Model-quality evaluation and runtime evaluation were treated as separate concerns.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;engineering-challenges&quot;&gt;Engineering Challenges&lt;/h3&gt;
&lt;p&gt;Several engineering challenges appeared during the implementation.&lt;/p&gt;
&lt;h4 id=&quot;1-correct-tensor-indexing&quot;&gt;1. Correct Tensor Indexing&lt;/h4&gt;
&lt;p&gt;The CUDA kernel had to reproduce the same logical output as the reference convolution operation.&lt;/p&gt;
&lt;p&gt;A small indexing error could generate incorrect feature maps without necessarily causing a runtime failure.&lt;/p&gt;
&lt;h4 id=&quot;2-memory-management&quot;&gt;2. Memory Management&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;im2col&lt;/code&gt; transformation created additional intermediate data.&lt;/p&gt;
&lt;p&gt;This required careful allocation, reuse, and release of GPU memory.&lt;/p&gt;
&lt;h4 id=&quot;3-synchronisation&quot;&gt;3. Synchronisation&lt;/h4&gt;
&lt;p&gt;Threads sharing data through shared memory had to synchronise at appropriate points.&lt;/p&gt;
&lt;p&gt;Missing or unnecessary synchronisation could cause incorrect results or reduced performance.&lt;/p&gt;
&lt;h4 id=&quot;4-kernel-configuration&quot;&gt;4. Kernel Configuration&lt;/h4&gt;
&lt;p&gt;Thread-block dimensions and tile sizes affected:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Occupancy&lt;/li&gt;
&lt;li&gt;Shared-memory usage&lt;/li&gt;
&lt;li&gt;Memory access&lt;/li&gt;
&lt;li&gt;Work distribution&lt;/li&gt;
&lt;li&gt;Overall kernel performance&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;5-pytorch-integration&quot;&gt;5. PyTorch Integration&lt;/h4&gt;
&lt;p&gt;The extension had to account for tensor shapes, data types, device placement, memory layout, compilation, linking, and output comparison.&lt;/p&gt;
&lt;h4 id=&quot;6-measuring-the-correct-bottleneck&quot;&gt;6. Measuring the Correct Bottleneck&lt;/h4&gt;
&lt;p&gt;Improving one component changed the relative cost of other components.&lt;/p&gt;
&lt;p&gt;Profiling therefore had to be repeated after major optimisations.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;engineering-lessons&quot;&gt;Engineering Lessons&lt;/h3&gt;
&lt;h4 id=&quot;1-optimising-one-component-does-not-guarantee-an-equivalent-end-to-end-improvement&quot;&gt;1. Optimising One Component Does Not Guarantee an Equivalent End-to-End Improvement&lt;/h4&gt;
&lt;p&gt;Once the encoder was accelerated, ConvLSTM represented a larger proportion of the remaining runtime.&lt;/p&gt;
&lt;h4 id=&quot;2-im2col-makes-convolution-gpu-friendly-but-introduces-memory-overhead&quot;&gt;2. &lt;code&gt;im2col&lt;/code&gt; Makes Convolution GPU-Friendly but Introduces Memory Overhead&lt;/h4&gt;
&lt;p&gt;The transformation enables GEMM-based execution while creating a larger intermediate representation.&lt;/p&gt;
&lt;h4 id=&quot;3-shared-memory-is-valuable-when-threads-repeatedly-reuse-nearby-data&quot;&gt;3. Shared Memory Is Valuable When Threads Repeatedly Reuse Nearby Data&lt;/h4&gt;
&lt;p&gt;Its benefit depends on access patterns, tile sizes, synchronisation, and thread-block configuration.&lt;/p&gt;
&lt;h4 id=&quot;4-profiling-must-guide-optimisation&quot;&gt;4. Profiling Must Guide Optimisation&lt;/h4&gt;
&lt;p&gt;Performance work should focus on measured bottlenecks rather than assumptions about which component is slow.&lt;/p&gt;
&lt;h4 id=&quot;5-correctness-is-as-important-as-speed&quot;&gt;5. Correctness Is as Important as Speed&lt;/h4&gt;
&lt;p&gt;GPU output should be compared against a trusted reference implementation before performance improvements are accepted.&lt;/p&gt;
&lt;h4 id=&quot;6-custom-pytorch-extensions-offer-flexibility-at-the-cost-of-additional-complexity&quot;&gt;6. Custom PyTorch Extensions Offer Flexibility at the Cost of Additional Complexity&lt;/h4&gt;
&lt;p&gt;Device handling, tensor shapes, data types, compilation, numerical validation, and fallback behaviour all require careful attention.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;h4 id=&quot;what-is-mscred-&quot;&gt;What is MSCRED?&lt;/h4&gt;
&lt;p&gt;MSCRED stands for &lt;strong&gt;Multi-Scale Convolutional Recurrent Encoder-Decoder&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It is an architecture designed to model relationships between multiple time-series signals and detect abnormal behaviour through reconstruction error.&lt;/p&gt;
&lt;h4 id=&quot;what-does-im2col-do-&quot;&gt;What does &lt;code&gt;im2col&lt;/code&gt; do?&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;im2col&lt;/code&gt; rearranges overlapping convolution windows into matrix columns, allowing convolution to be expressed as matrix multiplication.&lt;/p&gt;
&lt;h4 id=&quot;why-did-shared-memory-help-&quot;&gt;Why did shared memory help?&lt;/h4&gt;
&lt;p&gt;Shared memory allowed threads in the same block to reuse data without repeatedly loading it from slower global memory. This reduced memory-access overhead for reusable tiles.&lt;/p&gt;
&lt;h4 id=&quot;did-gpu-memory-optimisation-improve-the-f1-score-&quot;&gt;Did GPU memory optimisation improve the F1 score?&lt;/h4&gt;
&lt;p&gt;That was not the purpose of the optimisation, and this article does not claim that it did.&lt;/p&gt;
&lt;p&gt;The best recorded F1 score describes anomaly-detection quality in the thesis experiments. The CPU and GPU timing results describe computational performance. They should be interpreted separately.&lt;/p&gt;
&lt;h4 id=&quot;why-was-the-encoder-speed-up-much-larger-than-the-total-speed-up-&quot;&gt;Why was the encoder speed-up much larger than the total speed-up?&lt;/h4&gt;
&lt;p&gt;After the encoder became faster, ConvLSTM represented a larger share of total runtime. The overall system was therefore limited by the remaining bottleneck.&lt;/p&gt;
&lt;h4 id=&quot;is-a-custom-cuda-kernel-always-faster-than-pytorch-operations-&quot;&gt;Is a Custom CUDA Kernel Always Faster Than PyTorch Operations?&lt;/h4&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;Optimised PyTorch and CUDA libraries are already highly efficient for many operations. A custom kernel is most useful when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The workload has a specialised access pattern&lt;/li&gt;
&lt;li&gt;Existing operations create unnecessary overhead&lt;/li&gt;
&lt;li&gt;Profiling identifies a clear bottleneck&lt;/li&gt;
&lt;li&gt;Correctness and maintenance costs are justified&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;can-the-original-industrial-dataset-be-shared-&quot;&gt;Can the Original Industrial Dataset Be Shared?&lt;/h4&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;The original dataset contains industrial process information. A public reproduction would require synthetic or appropriately anonymised multivariate time-series data.&lt;/p&gt;
&lt;h4 id=&quot;are-these-benchmark-results-universal-&quot;&gt;Are These Benchmark Results Universal?&lt;/h4&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;Performance depends on GPU architecture, CPU configuration, tensor dimensions, batch size, kernel implementation, memory layout, CUDA and PyTorch versions, compiler settings, and workload characteristics.&lt;/p&gt;
&lt;p&gt;The reported numbers apply to the specific experimental setup described in this article.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;limitations-and-data-privacy-considerations&quot;&gt;Limitations and Data-Privacy Considerations&lt;/h3&gt;
&lt;p&gt;The reported results were obtained on a specific hardware configuration and workload.&lt;/p&gt;
&lt;p&gt;Different GPUs, tensor dimensions, batch sizes, memory layouts, CUDA versions, and kernel configurations may produce different results.&lt;/p&gt;
&lt;p&gt;The original dataset cannot be published because it contains industrial process information. A public reproduction would therefore require synthetic or appropriately anonymised multivariate time-series data.&lt;/p&gt;
&lt;p&gt;This article presents aggregate benchmark measurements and technical methodology rather than the complete industrial dataset or restricted implementation.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;future-work&quot;&gt;Future Work&lt;/h3&gt;
&lt;p&gt;Future work could focus on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Profiling and optimising the ConvLSTM component&lt;/li&gt;
&lt;li&gt;Reducing intermediate memory allocation&lt;/li&gt;
&lt;li&gt;Reducing host-to-device and device-to-device memory movement&lt;/li&gt;
&lt;li&gt;Comparing the custom implementation with optimised CUDA libraries&lt;/li&gt;
&lt;li&gt;Evaluating additional tile and thread-block configurations&lt;/li&gt;
&lt;li&gt;Testing across different GPU architectures&lt;/li&gt;
&lt;li&gt;Reproducing the workflow using synthetic public data&lt;/li&gt;
&lt;li&gt;Adding automated correctness and regression tests&lt;/li&gt;
&lt;li&gt;Measuring kernel-level execution using GPU profiling tools&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This work was completed as part of my M.Tech thesis research at the &lt;strong&gt;Indian Institute of Technology Bhilai&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It demonstrated how low-level GPU optimisation can be integrated into a deep-learning anomaly-detection pipeline.&lt;/p&gt;
&lt;p&gt;The convolutional encoder benefited substantially from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CUDA parallelisation&lt;/li&gt;
&lt;li&gt;&lt;code&gt;im2col&lt;/code&gt;-based lowering&lt;/li&gt;
&lt;li&gt;GEMM&lt;/li&gt;
&lt;li&gt;Shared-memory data reuse&lt;/li&gt;
&lt;li&gt;Custom PyTorch integration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Encoder runtime decreased from &lt;strong&gt;300 seconds to 23.664 seconds&lt;/strong&gt; in the measured workload.&lt;/p&gt;
&lt;p&gt;The broader result also illustrated an important systems principle: once one stage is accelerated, another stage may become the new performance bottleneck.&lt;/p&gt;
&lt;p&gt;In this case, ConvLSTM limited the end-to-end improvement even after the encoder achieved a substantial speed-up.&lt;/p&gt;
&lt;p&gt;The work reinforced that successful performance engineering requires both low-level optimisation and system-level measurement.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;references-1&quot;&gt;References&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Zhang, C., Song, D., Chen, Y., Feng, X., Lumezanu, C., Cheng, W., Ni, J., Zong, B., Chen, H., and Chawla, N. V. (2019). &lt;strong&gt;A Deep Neural Network for Unsupervised Anomaly Detection and Diagnosis in Multivariate Time Series Data.&lt;/strong&gt; &lt;em&gt;Proceedings of the AAAI Conference on Artificial Intelligence&lt;/em&gt;, 33(01), 1409–1416. &lt;a rel=&quot;nofollow&quot; href=&quot;https://doi.org/10.1609/aaai.v33i01.33011409&quot;&gt;https://doi.org/10.1609/aaai.v33i01.33011409&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shi, X., Chen, Z., Wang, H., Yeung, D.-Y., Wong, W.-K., and Woo, W.-C. (2015). &lt;strong&gt;Convolutional LSTM Network: A Machine Learning Approach for Precipitation Nowcasting.&lt;/strong&gt; &lt;em&gt;Advances in Neural Information Processing Systems 28&lt;/em&gt;, 802–810. &lt;a rel=&quot;nofollow&quot; href=&quot;https://proceedings.neurips.cc/paper_files/paper/2015/hash/07563a3fe3bbe7e3ba84431ad9d055af-Abstract.html&quot;&gt;https://proceedings.neurips.cc/paper_files/paper/2015/hash/07563a3fe3bbe7e3ba84431ad9d055af-Abstract.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NVIDIA. &lt;strong&gt;CUDA C++ Programming Guide.&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.nvidia.com/cuda/cuda-c-programming-guide/&quot;&gt;https://docs.nvidia.com/cuda/cuda-c-programming-guide/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PyTorch. &lt;strong&gt;Custom C++ and CUDA Operators.&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.pytorch.org/tutorials/advanced/cpp_custom_ops.html&quot;&gt;https://docs.pytorch.org/tutorials/advanced/cpp_custom_ops.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;citation-and-feedback&quot;&gt;Citation and Feedback&lt;/h3&gt;
&lt;p&gt;If this article informs academic or technical work, please cite the original MSCRED and ConvLSTM papers above.&lt;/p&gt;
&lt;p&gt;To refer specifically to my thesis implementation, optimisation process, or reported benchmarks, you may cite this article as:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Vutnoor, R. (2026). “GPU-Accelerating MSCRED with CUDA, im2col, GEMM, and a Custom PyTorch Extension.” DEV Community.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you found the article useful, a reaction, comment, or constructive technical question on DEV would be appreciated.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I am &lt;strong&gt;Ranjith Vutnoor&lt;/strong&gt;, an AI/ML Software Engineer and IIT Bhilai alumnus working on production RAG systems, LLM evaluation, model fine-tuning, PyTorch, and GPU-accelerated machine learning.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://ranjithvutnoor.github.io/portfolio/&quot;&gt;Portfolio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ranjithvutnoor&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/ranjithvutnoor/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26883/gpu-accelerating-mscred-with-cuda-im2col-gemm-and-a-custom-pytorch-extension</guid>
<pubDate>Fri, 11 Sep 2026 18:07:01 +0000</pubDate>
</item>
<item>
<title>Stop Recreating Database Connections: An Illustrated Guide to Connection Pooling</title>
<link>https://coderlegion.com/25256/stop-recreating-database-connections-an-illustrated-guide-to-connection-pooling</link>
<description>&lt;p&gt;If you have ever built an application that talks to a database, you have used a database connection. A database is simply a structured storage system for digital information, and a connection is the secure, active pipeline established between your application's code and that database. &lt;/p&gt;
&lt;p&gt;However, constantly opening and closing these connections for every single user request is incredibly slow and resource-heavy. To solve this, developers use a technique called &lt;strong&gt;connection pooling&lt;/strong&gt;. Connection pooling is a performance optimization method where a group (or &quot;pool&quot;) of database connections is kept open and idle, waiting to be reused by whoever needs them, rather than being destroyed and recreated on demand.&lt;/p&gt;
&lt;h4 id=&quot;the-analogy-the-pizza-delivery-fleet&quot;&gt;The Analogy: The Pizza Delivery Fleet&lt;/h4&gt;
&lt;p&gt;To understand why connection pooling is so vital, imagine you run a busy, high-volume pizza restaurant. Every time an order comes in, you need to deliver it to a customer. &lt;/p&gt;
&lt;p&gt;Imagine if, for every single delivery, you had to hire a brand-new driver, run a background check, sign a legal employment contract, set up their vehicle registration, and purchase a GPS. Once that single delivery was complete, you immediately fired them, canceled their insurance, and sold the car. It sounds absurd, right? The administrative overhead of hiring and firing would take three times longer than the actual delivery, and your restaurant would go bankrupt in a week.&lt;/p&gt;
&lt;p&gt;Instead, you hire a permanent, dedicated fleet of five delivery drivers. They sit in the break room, fully certified and ready. When a pizza is boxed, the first available driver grabs the box, drives to the house, delivers it, and returns to wait in the break room for the next order. &lt;/p&gt;
&lt;p&gt;In this analogy, the drivers are the &quot;connection pool,&quot; the deliveries are your database queries (requests for information), and the hiring process is the network handshake required to open a new database connection.&lt;/p&gt;
&lt;h4 id=&quot;why-it-matters-in-daily-software-engineering&quot;&gt;Why It Matters in Daily Software Engineering&lt;/h4&gt;
&lt;p&gt;In real-world software, opening a database connection is highly expensive in terms of computing power. The application must perform a security handshake, authenticate its username and password, allocate memory on the database server, and establish a network route. This process can easily take 50 to 100 milliseconds.&lt;/p&gt;
&lt;p&gt;If your website experiences a surge in traffic—say, 1,000 users clicking a button at the exact same moment—your server would try to open 1,000 separate connections simultaneously. The database server would quickly run out of memory, slow down to a crawl, and eventually crash, displaying a &quot;database connection error&quot; to your users.&lt;/p&gt;
&lt;p&gt;Connection pooling prevents this complete system collapse. It sets a strict limit on the maximum number of connections allowed (e.g., capping it at 20). If 1,000 users arrive, they politely share those 20 open connections. Because each connection is already established, queries execute in 1 to 2 milliseconds. Once a query is done, the connection is instantly recycled for the next user in line, protecting your database from crashing.&lt;/p&gt;
&lt;h4 id=&quot;a-simple-code-example-in-node-js&quot;&gt;A Simple Code Example in Node.js&lt;/h4&gt;
&lt;p&gt;Below is a simple JavaScript example using a popular PostgreSQL database driver. It demonstrates how we initialize a pool and query the database without manually opening and closing connections.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const { Pool } = require('pg');

// 1. Create a pool with a maximum capacity of 10 connections
const pool = new Pool({
  user: 'db_user',
  host: 'database.server.com',
  database: 'mydb',
  password: 'securepassword',
  port: 5432,
  max: 10, // Maximum connections in the pool
});

async function fetchUserData(userId) {
  // 2. Grab an already-open connection from the pool
  const client = await pool.connect();
  
  try {
    // 3. Execute the database query
    const result = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
    return result.rows[0];
  } catch (err) {
    console.error('Database query error:', err);
  } finally {
    // 4. Crucial: Release the connection back to the pool
    // This does NOT close the connection; it just makes it available for others!
    client.release();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-takeaway-4&quot;&gt;The Takeaway&lt;/h4&gt;
&lt;p&gt;Connection pooling turns a heavy, repetitive administrative chore into a fast, shared utility. By keeping a smart, limited buffer of active connections ready to work, software systems can handle tens of thousands of requests smoothly, ensuring high-speed page loads while protecting underlying servers from sudden traffic spikes.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;resources-1&quot;&gt;Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Saurav-TB-Pandey/react-hook-lab&quot;&gt;react-hook-lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;react-hook-lab:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect with me on LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/pandeysaurav/&quot;&gt;Saurav Pandey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/08/how-connection-pooling-speeds-up-your.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/saurav_tb_pandey/stop-recreating-database-connections-an-illustrated-guide-to-connection-pooling-4i64&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25256/stop-recreating-database-connections-an-illustrated-guide-to-connection-pooling</guid>
<pubDate>Fri, 11 Sep 2026 02:43:51 +0000</pubDate>
</item>
<item>
<title>Speed Up Your Queries: A Plain-English Guide to Database Indexing</title>
<link>https://coderlegion.com/25254/speed-up-your-queries-a-plain-english-guide-to-database-indexing</link>
<description>&lt;h4 id=&quot;what-is-database-indexing-&quot;&gt;What is Database Indexing?&lt;/h4&gt;
&lt;p&gt;Database indexing is a technique used by software systems to speed up the retrieval of data from a database table. By creating a separate, organized pointer structure, the database can locate specific records without having to search through every single row of data. Think of it as a specialized shortcut map for your digital filing cabinet.&lt;/p&gt;
&lt;h4 id=&quot;the-library-card-catalog-analogy&quot;&gt;The Library Card Catalog Analogy&lt;/h4&gt;
&lt;p&gt;Imagine walking into a massive metropolitan library containing 100,000 books. If you want to find a book titled &quot;The Midnight Mystery&quot; and there is no catalog or organization system, you would have to start at the first shelf in the corner and look at every single book cover one by one until you find it. This slow, painful process is what a database does during a &quot;full table scan.&quot;&lt;/p&gt;
&lt;p&gt;Now, imagine the library has an alphabetical card catalog. You walk straight to the drawer marked &quot;M,&quot; find the index card for &quot;The Midnight Mystery,&quot; and read the card. It tells you exactly which aisle, shelf, and position the book is on. You walk directly there and grab it in seconds. In this scenario, the card catalog is the index, the books are the rows of data in your table, and the physical location is the memory address of the database record.&lt;/p&gt;
&lt;h4 id=&quot;why-it-matters-daily-in-tech&quot;&gt;Why It Matters Daily in Tech&lt;/h4&gt;
&lt;p&gt;In the tech industry, engineers use database indexing to prevent application lag and catastrophic server crashes. Without indexes, as a company's database grows from thousands of rows to millions, simple daily actions like logging in, searching for an item, or pulling up a user profile would take seconds or even minutes instead of milliseconds.&lt;/p&gt;
&lt;p&gt;Slow database queries hog the system's central processing unit (CPU) and memory, causing other requests to back up and eventually freeze the entire application. By adding appropriate indexes, developers keep application programming interfaces (APIs) lightning-fast, reduce cloud hosting bills (because servers do not have to work as hard), and ensure a smooth user experience. However, engineers must find a balance: every index takes up storage space and slows down write operations (like adding or updating data), because the database has to update both the main table and the index every single time a change is made.&lt;/p&gt;
&lt;h4 id=&quot;how-it-works-in-practice&quot;&gt;How It Works in Practice&lt;/h4&gt;
&lt;p&gt;Here is a simple example in SQL (Structured Query Language). Imagine a system trying to look up a user by their email address.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Without an index, the database must scan every row in the users table
SELECT * FROM users WHERE email = 'developer@example.com';

-- To speed this query up, we create an index on the email column
CREATE INDEX idx_users_email ON users(email);

-- Now, the database uses the index to jump directly to the correct user
SELECT * FROM users WHERE email = 'developer@example.com';
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-takeaway-5&quot;&gt;The Takeaway&lt;/h4&gt;
&lt;p&gt;Database indexing is the ultimate balancing act of software performance optimization. It turns grueling, resource-heavy data searches into near-instant lookups, keeping applications snappy as they scale up to handle millions of users. Just remember that indexes are not free; treat them like directory signs in a physical store—use them where visitors frequently get lost, but do not clutter every wall with them, or you will slow down the inventory restocking process.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;resources-2&quot;&gt;Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Saurav-TB-Pandey/react-hook-lab&quot;&gt;react-hook-lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;react-hook-lab:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect with me on LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/pandeysaurav/&quot;&gt;Saurav Pandey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/08/the-secret-to-fast-apps-understanding.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/saurav_tb_pandey/speed-up-your-queries-a-plain-english-guide-to-database-indexing-21a3&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25254/speed-up-your-queries-a-plain-english-guide-to-database-indexing</guid>
<pubDate>Fri, 11 Sep 2026 02:43:19 +0000</pubDate>
</item>
</channel>
</rss>