<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Sumit M. on Medium]]></title>
        <description><![CDATA[Stories by Sumit M. on Medium]]></description>
        <link>https://medium.com/@sumit_m?source=rss-79e4d45fc1a2------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Wwn_2YEs2zMA9ZqC7t4myA.png</url>
            <title>Stories by Sumit M. on Medium</title>
            <link>https://medium.com/@sumit_m?source=rss-79e4d45fc1a2------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 22:22:59 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@sumit_m/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Local-First Apps Make Offline Support the Default, Not an Afterthought]]></title>
            <link>https://medium.com/@sumit_m/local-first-apps-make-offline-support-the-default-not-an-afterthought-f2a151317e8d?source=rss-79e4d45fc1a2------2</link>
            <guid isPermaLink="false">https://medium.com/p/f2a151317e8d</guid>
            <category><![CDATA[frontend-development]]></category>
            <category><![CDATA[design-systems]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[mobile-development]]></category>
            <category><![CDATA[software-architecture]]></category>
            <dc:creator><![CDATA[Sumit M.]]></dc:creator>
            <pubDate>Wed, 11 Nov 2026 05:33:04 GMT</pubDate>
            <atom:updated>2026-11-11T05:33:04.920Z</atom:updated>
            <content:encoded><![CDATA[<p>A user never tells me they want “offline mode.” They tell me a save button did nothing on a train, or a field app dropped work in a basement. That is the real requirement for local-first apps and offline-first mobile architecture: if the action is valid, I persist it locally first and sync later when the network recovers.</p><p>I learned this the hard way after shipping network-first flows that looked clean in diagrams and failed under weak connectivity. We returned success before durable write confirmation, and users lost edits when the tab crashed. The problem was architectural: I treated the API as the gatekeeper for interactions that should have been durable on-device.</p><h3>Why offline must be a product default</h3><p>Users do not experience architecture diagrams. They experience whether a tap works under packet loss, captive portals, and radios that wake slowly. In practice, I treat offline support as a product default because UI fragility is visible immediately, while backend consistency can converge later if I design for it intentionally.</p><p>Local-first changes the contract. The device store becomes the operational source of truth for reads and writes, and the network becomes reconciliation infrastructure. That shifts failure handling, loading states, and retry semantics into the frontend architecture, not just the API tier.</p><blockquote><strong>Note: </strong>A local-first app is not the same as a cached app. A cache optimizes reads; a local database can durably capture user intent.</blockquote><p>I usually frame this as customer truth vs. platform constraint:</p><ul><li><strong>Customer truth:</strong> a valid action should not disappear because the network is weak.</li><li><strong>Platform constraint:</strong> global consistency is slower and sometimes unavailable.</li></ul><p>That framing points to the system shape. The clearest way to see it is in the write path below.</p><p>Before I go deeper, this diagram shows the mental model I use when reviewing local-first designs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kOaxCk44PywSw62thIIvwA.png" /><figcaption>Local-first makes local durability the first step</figcaption></figure><h3>How local-first actually works</h3><p>The core loop is simple: the UI reads from a local database, every mutation is persisted locally before I show success, and a background sync engine drains a durable outbound queue when connectivity allows. The queue is not an implementation detail. It is the contract that keeps retries safe after app restarts, tab crashes, or device reboots.</p><blockquote><strong>Practical tip:</strong> If a mutation is not durably queued, it is not offline-safe. Optimistic UI alone is not enough.</blockquote><p>I need the backend to cooperate. APIs should support idempotency keys, stable resource identifiers, and explicit version metadata. For ordering, I prefer <a href="https://cse.buffalo.edu/tech-reports/2014-04.pdf">Hybrid Logical Clocks</a> over naive wall-clock timestamps because clock skew and device sleep make simple time ordering unreliable. HLCs give monotonic ordering without pretending the client clock is trustworthy, but I still treat them as one input to conflict handling, not the whole answer.</p><p>If you want a concrete frontend foundation, patterns from <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">MDN IndexedDB documentation</a> and <a href="https://www.sqlite.org/docs.html">SQLite documentation</a> are still practical. For readers who want a guided overview of frontend system design, Educative has a path on state boundaries. The implementation contract is easier to discuss in a mutation pipeline.</p><p>Before I talk about failure modes, here is the minimum write pipeline I expect in production.</p><pre>type MutationIntent&lt;T&gt; = {<br>  id: string;<br>  entityId: string;<br>  patch: Partial&lt;T&gt;;<br>  hlc?: string;<br>  version?: number;<br>};<br><br>type OutboundOperation&lt;T&gt; = {<br>  intent: MutationIntent&lt;T&gt;;<br>  idempotencyKey: string;<br>  status: &quot;pending&quot; | &quot;acked&quot; | &quot;failed&quot;;<br>  attempts: number;<br>};<br><br>type ServerAck&lt;T&gt; = {<br>  entityId: string;<br>  serverVersion?: number;<br>  hlc?: string;<br>  mergedState: T;<br>};<br><br>class MutationPipeline&lt;T extends Record&lt;string, any&gt;&gt; {<br>  private localStore = new Map&lt;string, T &amp; { syncStatus?: string; hlc?: string; version?: number }&gt;();<br>  private outboundQueue: OutboundOperation&lt;T&gt;[] = [];<br><br>  async mutate(entityId: string, patch: Partial&lt;T&gt;): Promise&lt;void&gt; {<br>    const current = this.localStore.get(entityId) || ({} as T);<br>    const intent: MutationIntent&lt;T&gt; = {<br>      id: crypto.randomUUID(),<br>      entityId,<br>      patch,<br>      hlc: this.nextHlc(),<br>      version: (current as any).version<br>    };<br><br>    // Persist intent locally before any network attempt.<br>    this.localStore.set(entityId, { ...current, ...patch, syncStatus: &quot;pending&quot;, hlc: intent.hlc, version: intent.version } as any);<br><br>    // Durable outbound queue for retry/replay.<br>    const operation: OutboundOperation&lt;T&gt; = {<br>      intent,<br>      idempotencyKey: intent.id,<br>      status: &quot;pending&quot;,<br>      attempts: 0<br>    };<br>    this.outboundQueue.push(operation);<br><br>    await this.flushQueue();<br>  }<br><br>  async flushQueue(): Promise&lt;void&gt; {<br>    for (const op of this.outboundQueue.filter(o =&gt; o.status === &quot;pending&quot;)) {<br>      try {<br>        op.attempts += 1;<br><br>        const ack = await this.sendToServer(op.intent, op.idempotencyKey);<br>        this.reconcileAck(ack);<br>        op.status = &quot;acked&quot;;<br>      } catch {<br>        // Keep pending for retry; caller can backoff/retry later.<br>      }<br>    }<br>  }<br><br>  private reconcileAck&lt;TState extends T&gt;(ack: ServerAck&lt;TState&gt;): void {<br>    const local = this.localStore.get(ack.entityId);<br>    if (!local) return;<br><br>    // Reconcile server-authoritative state and clear pending flag.<br>    this.localStore.set(ack.entityId, {<br>      ...(local as any),<br>      ...ack.mergedState,<br>      syncStatus: &quot;synced&quot;,<br>      hlc: ack.hlc ?? (local as any).hlc,<br>      version: ack.serverVersion ?? (local as any).version<br>    });<br>  }<br><br>  private async sendToServer(intent: MutationIntent&lt;T&gt;, idempotencyKey: string): Promise&lt;ServerAck&lt;T&gt;&gt; {<br>    // Placeholder for API call with idempotency key and version/HLC metadata.<br>    return {<br>      entityId: intent.entityId,<br>      mergedState: { ...((this.localStore.get(intent.entityId) || {}) as T), ...intent.patch } as T,<br>      serverVersion: (intent.version ?? 0) + 1,<br>      hlc: intent.hlc<br>    };<br>  }<br><br>  private nextHlc(): string {<br>    return `${Date.now()}-${Math.random().toString(16).slice(2)}`;<br>  }<br>}<br>}</pre><h3>Where sync systems fail in production</h3><p>Most teams do not fail because they forgot a cache. They fail because they under-scoped sync until production exposed out-of-order delivery, duplicate operations, and silent divergence. I have seen systems look correct in happy-path demos and then corrupt trust when a reboot happened mid-flush or when partial writes crossed a schema boundary.</p><p>The list I insist on design-reviewing is short:</p><ul><li>out-of-order delivery and duplicate delivery</li><li>storage pressure, queue corruption, and partial writes</li><li>offline edits on multiple devices against the same record</li></ul><blockquote><strong>Watch out:</strong> “Last write wins” is a product decision, not a neutral default. It can silently discard valid user work.</blockquote><p>Conflict resolution is where product and architecture meet. Use LWW for low-stakes metadata, CRDTs for commutative counters or shared docs, and server arbitration when business rules matter more than client autonomy. CRDTs give deterministic merges for some data types, with storage and model complexity. Operational transformation can fit collaborative text, but it raises implementation risk. I usually start with the least powerful model that preserves trust and recoverability.</p><p>Before I move to migrations, this comparison is the design aid I wish more teams used early.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/727/1*QB35IC_3Kwh_ADc409s6nw.png" /></figure><h3>The migration problem teams miss</h3><p>The ugly case is not today’s session. It is the device that stays offline for weeks, accumulates writes on an old app version, upgrades, and then reconnects to a newer server contract. If I ignore that path, schema evolution becomes a one-way door because replaying queued operations can corrupt local state or drop fields the server now requires.</p><blockquote><strong>Note:</strong> Forward compatibility matters more than elegance here. Old queued operations will outlive your clean abstractions.</blockquote><p>I want explicit versioning in both local schema and sync payloads. I also want migrations that preserve unsynced records, compatibility adapters for replay, and reject paths that fail loudly instead of silently truncating data. A concrete example: if a queued mutation still sends status: “pending_review” after the server renamed that enum value to awaiting_review, replay needs a versioned adapter instead of a blind write. <a href="https://json-schema.org/">JSON Schema</a> can help document payload evolution, and Educative’s<a href="https://www.educative.io/courses/building-progressive-web-applications?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=building_progressive_web_applications&amp;eid=5082902844932096"> Building Progressive Web Applications</a> covers the IndexedDB and background sync foundations underneath this pattern, and the<a href="https://www.educative.io/courses/advanced-typescript-masterclass?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=advanced_typescript_masterclass&amp;eid=5082902844932096"> Advanced TypeScript Masterclass</a> shows how discriminated unions can make each payload version explicit in your types.</p><p>The risky transitions are easier to reason about as a sequence, so I would document them visually before launch.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*v46j_Nx6Cwg69Vp2KmPrkw.png" /><figcaption>Long-offline clients make schema evolution operational, not theoretical.</figcaption></figure><h3>What I would measure before scaling</h3><p>I do not treat local-first as responsible unless I instrument it like a platform. The first dashboard I want includes sync latency distribution, queue depth and age, durable write failure rate, conflict frequency, storage growth against retention policy, migration failure rate, and the percentage of clients on stale schemas. Without that, I am arguing from anecdotes.</p><blockquote><strong>Practical tip:</strong> Measure queue age percentiles, not just averages. A small tail of week-old operations usually hides your real risk.</blockquote><p>I also want crisp decision records for accepted risk. Which failures are reversible? Which are one-way doors? For example, I may accept imperfect conflict UX in v1 if every mutation is durably stored and recoverable. I will not accept silent data loss, because users rarely forgive invisible corruption.</p><p>There is also a practical scaling point on the frontend. Long-lived local databases need compaction, tombstone policies, and bounded retention. If I store every operation forever, I trade sync reliability for unbounded disk growth and slower startup. In practice, most teams handle it with snapshots plus replay windows, then verify correctness with periodic server reconciliation.</p><p>For teams building toward this model, I often recommend pairing implementation work with reading on browser storage behavior and a solid grounding in distributed systems trade-offs, such as Educative’s<a href="https://www.educative.io/courses/grokking-the-system-design-interview?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=grokking_modern_system_design&amp;eid=5082902844932096"> Grokking Modern System Design</a> course. It keeps the conversation grounded in contracts, not hype.</p><h3>Conclusion</h3><p>In most products where user edits must survive network loss, local-first is the safer default and the heart of offline-first mobile architecture. It is a frontend reliability model that starts by capturing user intent durably on-device. The hard parts are sync strategies, conflict resolution, and migration safety, not rendering or caching. Before calling it production-ready, I want three things: durable local writes, replay-safe migrations, and metrics that prove sync is converging instead of quietly failing.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f2a151317e8d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Third-Party Scripts Quietly Eat Your Frontend Performance Budget]]></title>
            <link>https://medium.com/@sumit_m/third-party-scripts-quietly-eat-your-frontend-performance-budget-dc0173b27ecc?source=rss-79e4d45fc1a2------2</link>
            <guid isPermaLink="false">https://medium.com/p/dc0173b27ecc</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[frontend-development]]></category>
            <category><![CDATA[core-web-vitals]]></category>
            <category><![CDATA[web-performance]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Sumit M.]]></dc:creator>
            <pubDate>Wed, 04 Nov 2026 07:20:29 GMT</pubDate>
            <atom:updated>2026-11-04T07:20:29.781Z</atom:updated>
            <content:encoded><![CDATA[<p>I have reviewed enough performance traces to recognize a common pattern in third-party-script performance: the code that hurts the page most is often code my team did not write. On one launch, we added a chat widget to a product page and saw LCP regress by roughly 300 ms in Lighthouse. The widget script also pulled in two additional vendor domains, and the Performance trace showed a burst of main-thread work just as the page was trying to settle.</p><p>Third-party scripts are JavaScript files loaded from another company’s domain. That sounds harmless until I treat the page as a shared budget: my code and rented code compete for the same network, CPU, and main thread. Usually, the problem is not a single bad decision. The problem is that, six months later, nobody owns the tag.</p><h3>Why third-party scripts slow pages</h3><p>A simple way to think about third-party scripts is that each one adds some mix of four costs: bytes to download, code to parse, code to execute, and extra requests triggered later. In practice, execution time is often the cost teams underestimate. A 30 KB script can still create a few hundred milliseconds of main-thread work on a slower phone. In large systems, I have also seen fourth-party calls, where one vendor quietly loads two more vendors that I did not approve directly.</p><p>Those costs show up in user-facing metrics. Core Web Vitals and third-party scripts intersect most clearly here. LCP can slip when long tasks delay rendering of the largest element or block work needed to paint it. INP gets worse when third-party JavaScript ties up the main thread and delays event handlers or visual updates after an input. Total Blocking Time is useful in lab runs because it acts as a proxy for that kind of contention, even though it is not a field metric.</p><blockquote><strong>Note:</strong> I have seen small analytics callbacks block input for more than 100 ms on low-end devices when they fired during a tap or click.</blockquote><p>Before this gets too abstract, the following comparison makes the browser work easier to see.</p><h4>Performance phases vs browser behavior, symptoms, and impacted metrics</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/634/1*4RbLjw1zvwOyUjzwqrwBHA.png" /></figure><h3>Start by finding everything loaded</h3><p>I do not try to optimize what I have not inventoried. Third-party scripts hide in plain sight: hardcoded &lt;script&gt; tags, tag managers, dynamically injected loaders, and vendor SDKs that fetch more resources later. If I scan only source files, I miss a meaningful part of what users actually run.</p><p>My basic inventory is small but strict:</p><ul><li>script name and business purpose</li><li>owner, page scope, and review date</li><li>how it enters the page</li><li>network domains touched</li><li>whether it runs on all routes or only some</li><li>whether it affects consent, state, or security</li><li>measured cost in bytes, CPU time, and long tasks</li><li>planned removal or reevaluation date</li></ul><blockquote><strong>Practical tip:</strong> If a script has no clear owner, I treat it as a removal candidate until someone proves otherwise.</blockquote><p>I learned this lesson the hard way at scale. A marketing pixel added for a short campaign was still firing on every route months after the campaign ended because nobody had recorded when to remove it. That kind of script inventory is the foundation for auditing third-party scripts and deciding whether to remove unused third-party code.</p><h3>Use DevTools to measure real cost</h3><p>Start with Chrome DevTools. In the Network tab, I look for third-party domains, request counts, priority, and whether the same script loads on every route by default. I also look for duplicate vendor requests on route changes, scripts fetched early but executed much later, and vendors that trigger additional third-party requests after the first load. Then I move to the Performance panel to find long tasks, long purple scripting blocks, and gaps between download time and execution time.</p><p>Lighthouse is useful for a fast baseline, especially for render-blocking resources and unused JavaScript. I do not stop there. Lighthouse is a starting point, not the full picture. Field data tells you whether the change actually helped users. <a href="https://www.educative.io/courses/learn-javascript?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=if_you_need_a_refresher_on_how_the_browser_loads_and_schedules_javascript&amp;eid=5082902844932096">If you need a refresher on how the browser loads and schedules JavaScript</a>, this is the point where a focused frontend-performance or <a href="https://www.educative.io/path/become-a-web-developer?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=browser_loading_course_on_educative_fits_naturally&amp;eid=5082902844932096">browser-loading course on Educative fits naturally.</a></p><blockquote><strong>Watch out:</strong> Transfer size is only part of the cost. Parse and execution time often matter more when you are trying to reduce main-thread blocking.</blockquote><p>If I have real-user monitoring, I validate changes in the field. A script that looks fine on my laptop can still regress INP for users on slower phones. That is often how I answer the question of which third-party scripts are hurting my INP metric, because the trace shows exactly when a vendor callback collides with user input. The next image shows why execution timing matters more than file size alone.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jc6eZ3t6jjvABNGA5GCuGw.png" /></figure><h3>Do not judge scripts by size alone</h3><p>A common mistake is treating small files as cheap files. A small analytics tag that hooks into a click handler can feel worse than a larger support widget loaded later because responsiveness depends on when CPU work happens. I have seen traces where the file itself was small, but the callback ran during an interaction and added visible input delay.</p><p>I use async and defer, but I do not treat them as magic. In async vs. defer third-party-script decisions, defer preserves script order and waits until HTML parsing is finished before execution. async does not preserve order and runs when the file is ready. Neither attribute removes parse cost, expensive execution, or third-party waterfall behavior once the script starts loading more code.</p><blockquote><strong>Note:</strong> Changing when a script runs can help a great deal, but it does not make JavaScript free.</blockquote><p>That trade-off becomes clearer when I compare typical script patterns side by side.</p><h4>Comparison of common third-party component types</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/744/1*ZJB8r1jD9cLnJulMiOiK8A.png" /></figure><h3>Ask what needs to load now</h3><p>Once I know the cost, I ask a product question: what must load before the user can do the next important thing? My default heuristic is simple. If a feature is not needed for the first meaningful task, defer it until idle time or until the user shows intent. For many pages, chat, maps, social embeds, and some experiments do not need first-load priority. One exception is support chat on checkout or account-recovery flows, where immediate availability may matter.</p><p>My default playbook is simple:</p><ul><li>use defer or async where ordering allows</li><li>lazy-load widgets below the fold</li><li>trigger heavy tools on user intent</li></ul><p>This is usually where I optimize third-party JavaScript with the least drama. In practice, that can mean that you lazy-load third-party embeds, defer social embeds to improve LCP, or lazy-load a chat widget only when the user interacts. Tag managers deserve an honest review here. They help teams move faster, which is often the right trade-off. They also reduce friction for site-wide script sprawl, so I add guardrails, review rules, and route-level scope instead of trusting them blindly. I have seen a tag manager load the same vendor on every route when the tag was needed only on one campaign landing page. Because it bypassed the normal code-review path, it stayed there longer than it should have.</p><blockquote><strong>Practical tip:</strong> Start with one page and one script. Measure before and after so the team trusts the change.</blockquote><p>At this point, the question becomes what stays, what moves, and who signs off.</p><pre>function bootApp({ route, isLoggedIn }) {<br>  // Defer analytics to reduce startup contention with critical app work<br>  requestIdleCallback(() =&gt; loadAnalytics(), { timeout: 2000 });<br><br>  // Wait for user intent before loading the chat widget<br>  const chatButton = document.querySelector(&quot;[data-open-chat]&quot;);<br>  if (chatButton) {<br>    chatButton.addEventListener(&quot;click&quot;, () =&gt; loadChatWidget(), { once: true });<br>  }<br><br>  // Load sensitive payment integrations only on pages that need them<br>  if (route.startsWith(&quot;/checkout&quot;) || route.startsWith(&quot;/billing&quot;)) {<br>    loadPaymentScript();<br>  }<br><br>  if (isLoggedIn) {<br>    hydrateUserMenu();<br>  }<br>}<br><br>function loadAnalytics() {<br>  const script = document.createElement(&quot;script&quot;);<br>  script.src = &quot;https://cdn.example.com/analytics.js&quot;;<br>  script.async = true;<br>  document.head.appendChild(script);<br>}<br><br>function loadChatWidget() {<br>  const script = document.createElement(&quot;script&quot;);<br>  script.src = &quot;https://cdn.example.com/chat-widget.js&quot;;<br>  script.async = true;<br>  document.head.appendChild(script);<br>}<br><br>function loadPaymentScript() {<br>  const script = document.createElement(&quot;script&quot;);<br>  script.src = &quot;https://payments.example.com/sdk.js&quot;;<br>  script.async = true;<br>  document.head.appendChild(script);<br>}<br><br>function hydrateUserMenu() {<br>  // Placeholder for lightweight authenticated UI setup<br>}<br><br>bootApp({ route: window.location.pathname, isLoggedIn: true });<br>  }</pre><h3>Decide what stays and who owns it</h3><p>Not every third-party script is bad. My question is whether each script is earning its place in the budget. I write down five things: the customer or business problem it solves, the owner, the page scope, the measured performance cost, and whether the decision is reversible. I also want an operational rule, such as a quarterly review or a budget threshold that forces reevaluation when a script adds too much CPU time or too many requests. That is the practical shape of a performance budget for third-party scripts.</p><p>This is also where performance meets trust. Payment widgets, consent managers, and checkout tags can affect PCI scope, data handling, and attack surface, depending on how they are integrated and which trust boundary they cross. If a script can observe or influence cardholder-data flows, the review bar changes. A current inventory, a well-scoped CSP, and visibility into fourth-party calls are part of basic operational hygiene.</p><blockquote><strong>Watch out:</strong> A vendor approved for checkout should not silently become a site-wide dependency.</blockquote><p>To make the trade-offs concrete, I like to use a simple governance table before any rollout review.</p><h4>Governance Table</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/807/1*G4xGGdwSjVL2EnaQwOhS-w.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/810/1*gxhZMtqR9OH9QQAFDlEu0Q.png" /></figure><h3>Make script reviews a team habit</h3><p>The root problem is usually not tooling. It is the operating model. If I do not schedule script reviews, assign owners, and include third-party JavaScript in the performance budget, accumulation wins by default.</p><p>The workflow that holds up is simple: inventory what loads, measure what it costs, prioritize by user impact, decide what can move off the critical path, and assign an owner with a review date. I have seen the payoff from both sides. One team removed an abandoned marketing tag and reduced a cluster of long tasks on key landing pages. Another team kept a costly script because it supported a high-value checkout flow, but only after scoping it to the right routes and giving the team a kill switch.</p><p>That is usually enough to change the conversation. Third-party scripts stop being just tags and start being dependencies with cost, risk, and an owner. When teams treat third-party-script performance as part of the same frontend budget as their own code, they make better decisions about what to defer, what to scope, and what to remove.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dc0173b27ecc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Hallucinated AI Function Can Look Right and Still Be Wrong]]></title>
            <link>https://medium.com/@sumit_m/a-hallucinated-ai-function-can-look-right-and-still-be-wrong-f26cd59c0a27?source=rss-79e4d45fc1a2------2</link>
            <guid isPermaLink="false">https://medium.com/p/f26cd59c0a27</guid>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[ai-coding]]></category>
            <category><![CDATA[backend-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[design-systems]]></category>
            <dc:creator><![CDATA[Sumit M.]]></dc:creator>
            <pubDate>Tue, 03 Nov 2026 09:52:16 GMT</pubDate>
            <atom:updated>2026-11-03T09:52:16.888Z</atom:updated>
            <content:encoded><![CDATA[<p>The most dangerous AI code is not the code that fails to compile. It is the function that looks plausible, satisfies the interface, clears the happy path, and quietly violates a contract that your production system depends on. I have seen teams celebrate the speed of generation, then spend the next week tracing a defect that appears only with real tenant data, real retries, or real authorization scopes.</p><p>In backend systems, silent semantic failure is the hard problem. Developers want faster output, and I understand that pressure, but the platform absorbs the blast radius when generated code invents a dependency, drops audit behavior, or assumes an API contract that was never guaranteed. I keep coming back to the same trade-off: product teams want speed, while platform teams carry the cost when generated code is wrong in subtle ways. That is where code hallucination becomes operationally expensive.</p><p>At Microsoft, and later at Meta and Databricks, the recurring lesson was simple: loud failures are cheaper than quiet ones. A compiler error gives you a bounded fix. A hallucinated function in a request path can survive review, tests, and deployment, then fail only when production traffic hits the one branch that your mocks never covered. That is where I want to start, because the rest of the architecture follows from that premise.</p><p>To make that failure pattern concrete, I would map the full validation path rather than focus on any single tool.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pt-p5XVKtzMCClmtCte1Yw.png" /><figcaption>A layered architecture diagram of AI code generation flowing into backend validation gates</figcaption></figure><h3>Why compiling does not mean working</h3><p>I push back when teams define hallucination as mostly a syntax failure. From a backend perspective, the dangerous class is semantic drift: code that type-checks, uses valid symbols, and still calls a deprecated endpoint, depends on an unapproved package, or breaks an invariant at a service boundary. Compilation tells me that the code is structurally acceptable, not operationally safe.</p><p>I learned this the hard way while debugging a generated patch that passed local tests but ignored a pagination-token edge case. The function returned correct results for ten rows and incorrect results for ten thousand. Nothing in the build failed, but the downstream service saw duplicate fetches, then rate limiting, then retries that amplified load. It was a practical example of confident but incorrect code output.</p><blockquote><strong>Watch out:</strong> A passing build can hide boundary-condition defects. Backend incidents often start with “all tests passed” and end with “the contract was wrong.”</blockquote><p>The gap that matters is the delta between the test pass rate and the production failure rate. That delta is where local correctness diverges from systemic correctness. If you want a practical model for contract-first thinking, I like how the <a href="https://spec.openapis.org/oas/latest.html">OpenAPI specification documentation</a> frames explicit interfaces. By contract-first, I mean defining request and response behavior up front so that both humans and tools have something concrete to validate against. Ambiguity is where generated code starts inventing facts.</p><p>The useful question is not whether the code compiles. The useful question is which layer can still prove it wrong.</p><p>Before I go deeper, it helps to compare the major validation layers side by side.</p><h4>Comparison of validation layers</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/629/1*-VDQKSsE9VSkzjoozrxHcw.png" /></figure><h3>A real-world frontend hallucination story</h3><p>I once watched a generated client helper demo perfectly and still create a backend problem. The function mirrored the response shape from a mocked API, handled loading states cleanly, and looked reviewable. What it quietly assumed, though, was that an empty page meant completion rather than a transient authorization filter on the server side.</p><p>That assumption forced server-side compensation later. The backend had to distinguish true pagination exhaustion from scope-filtered emptiness, then preserve retry semantics without leaking data across tenants. The root bug looked like frontend convenience, but the expensive fix landed in the API layer because the original generated helper encoded a contract that the service never promised. In AI systems, this kind of tool hallucination often starts with a believable client assumption rather than an obvious crash.</p><ul><li><strong>Mocks helped:</strong> The generated function matched the stub.</li><li><strong>Mocks hurt:</strong> The stub omitted authorization and pagination edge behavior.</li></ul><blockquote><strong>Note:</strong> Mock-based confidence is a weak signal for shared contracts. I trust traces and contract fixtures more than polished demos.</blockquote><p>This is why I treat generated code as a consumer of backend guarantees, not as a source of them. If the consumer invents semantics, the service pays later. That is also the bridge to backend validation: once a client starts assuming behavior that you never promised, the server needs guardrails that can reject or contain the mistake before it spreads.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2gNEYGTcwZS5uSObL8CJdA.png" /><figcaption>A sequence diagram showing a generated frontend function calling an API with a subtle contract mistake</figcaption></figure><h3>Catching backend bugs before production</h3><p>When a team asks me for a practical validation architecture, I do not start with prompt engineering. I start with layered controls that fail differently. Static analysis catches obvious misuse, a strict type system narrows ambiguity, dependency-registry verification rejects phantom or deprecated packages, and sandbox execution against representative fixtures exposes behavior that unit tests often miss.</p><p>By sandbox execution, I mean running the generated code in an isolated environment with realistic fixtures, network rules, and dependency constraints so that you can observe behavior without risking production systems. That matters because a lot of generated code looks fine until it meets real authorization scopes, data volume, or retry pressure.</p><p>I also want review scoped to risk, not aesthetics. Generated code should be checked aggressively for authorization paths, side effects, error handling, idempotency, and audit gaps. If a change touches a write path, I care more about whether retries are safe than whether the naming is clean. This is also where examples of code hallucinations that compile but fail become most useful, because they remind teams that passing syntax and passing production are different thresholds.</p><h3>A stack I trust in production</h3><ul><li><strong>Static and type checks:</strong> Cheap, fast, and necessary.</li><li><strong>Registry and contract checks:</strong> Catch unsupported dependencies and drift.</li><li><strong>Sandbox and policy checks:</strong> Validate runtime behavior under realistic constraints.</li></ul><p>I apply the same thinking to the generation workflow itself. If you operationalize model calls inside CI or internal tooling, that pipeline becomes a production dependency and deserves timeouts, fallbacks, and <a href="https://learn.microsoft.com/azure/architecture/patterns/circuit-breaker">circuit-breaker behavior</a>. Each layer is a reversible investment. Trusting one gate as sufficient is usually a one-way operational mistake.</p><p>To make that less abstract, the next step is wiring the stack into CI with clear failure conditions. Before that, it helps to look at one area where generated code fails in especially familiar ways: database access.</p><pre>const ciPipeline = () =&gt; {<br>  // 1. Run linting on the generated code<br>  executeCommand(&#39;npm run lint&#39;);<br><br>  // 2. Perform type checking<br>  executeCommand(&#39;npm run type-check&#39;);<br><br>  // 3. Verify dependency registry<br>  executeCommand(&#39;npm run verify-deps&#39;);<br><br>  // 4. Execute sandbox tests<br>  executeCommand(&#39;npm run test:sandbox&#39;);<br><br>  // 5. Conduct contract testing<br>  executeCommand(&#39;npm run test:contract&#39;);<br><br>  // 6. Apply policy gate for dependency and authorization checks<br>  const policyGate = checkPolicyGate();<br>  if (!policyGate.isSuccess) {<br>    throw new Error(&#39;Policy gate failed: unapproved dependency or insufficient auth coverage&#39;);<br>  }<br>};<br><br>const executeCommand = (command: string) =&gt; {<br>  console.log(`Executing: ${command}`);<br>  // Logic to execute the command and handle errors<br>};<br><br>const checkPolicyGate = () =&gt; {<br>  // Placeholder implementation; apply actual checks<br>  return { isSuccess: true };<br>};</pre><p><em>A CI pipeline snippet for code quality checks and policy enforcement</em></p><h3>The danger of generic database queries</h3><p>One of the most common backend hallucinations is a query that is valid SQL and invalid product behavior. I routinely see generated data-access code omit tenant filters, skip soft-delete predicates, assume nullability that the schema does not guarantee, or write without idempotency keys. These are not syntax problems. They are boundary violations hidden inside familiar query shapes.</p><p>This matters because data correctness is not only about the rows returned. In a product system, I need to prove who could access what, under which policy, with which audit event, and whether retry behavior stays safe under load. A broad SELECT in a local test can become a privacy incident once production cardinality and replication lag enter the picture.</p><blockquote><strong>Practical tip:</strong> Treat every generated query as under-scoped until proven otherwise. Tenant filters, pagination limits, and audit events should be explicit, not implied.</blockquote><p>I have found that showing the naive pattern next to the hardened one changes behavior faster than any guideline. The same principle carries into API routing, where weak assumptions at the edge tend to be amplified across every caller.</p><h4>Naive vs. hardened database query patterns (before and after)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/683/1*ToNHYqgcn56L6GFQFquujA.png" /></figure><h3>Implementing boundary checks in your API routing</h3><p>If I had to choose one high-leverage place to constrain generated code, I would choose the API boundary. Routing layers are where attractive assumptions meet reality: malformed inputs, deprecated fields, missing authorization scopes, inconsistent downstream enums, and clients pinned to older contract versions. Prompts can reduce ambiguity, but runtime policy is what stops bad assumptions from spreading.</p><p>In practice, I want explicit range checks, schema validation, scope enforcement, version-aware contract guards, and defensive handling for downstream inconsistency. I also want rate-limit expectations encoded close to ingress, because retries without backpressure become multiplicative under failure. Product teams want routine endpoints to stay fast. Platform teams need bad handler behavior at the edge to stop there instead of propagating through the system. This is one of the most reliable ways to detect semantic errors in AI tool calls before they create wider damage.</p><p>For teams building this muscle, I often recommend studying <a href="https://json-schema.org/">JSON Schema validation behavior</a> and pairing it with a strong framework contract. For Python teams, the <a href="https://fastapi.tiangolo.com/">FastAPI request-validation docs</a> are a practical reference. If you want more structured practice after working through those ideas, Educative’s<a href="https://www.educative.io/path/zero-to-hero-in-back-end-web-development?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=backend_development_path&amp;eid=5082902844932096"> backend development path</a> and<a href="https://www.educative.io/courses/design-and-build-great-web-api?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=web_api_design_course&amp;eid=5082902844932096"> web API design course</a> are useful for turning these guardrails into day to day habits.</p><p>The routing path becomes clearer when I show which guardrail catches which failure. It also sets up the next step: making the model work from tests and examples instead of from vague intent.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NDdcUkMC1lKpVUv3Px2Cpw.png" /><figcaption>A CI pipeline snippet for code quality checks and policy enforcement</figcaption></figure><h3>Adopting test-first prompting</h3><p>I support test-first prompting, but not as an ideology. I use it as workflow control. Before I ask a model for implementation, I want tests, fixtures, and contract examples that define the expected behavior, because those artifacts narrow ambiguity and can be reused by CI without interpretation.</p><p>The trade-off is real. For an intermediate engineer, this feels slower in the first week because you are writing constraints before seeing output. But in backend systems that handle permissions, money, or customer records, shifting effort left is usually cheaper than debugging after the merge. That is not theory. It is a repeated pattern from product teams that had to absorb support cost and incident load.</p><ul><li><strong>Prompt less broadly:</strong> Ask for code after behavior is explicit.</li><li><strong>Reuse artifacts:</strong> Tests become validation, documentation, and a decision record.</li></ul><blockquote><strong>Note:</strong> Test-first prompting is not about perfect prompts. It is about reducing the space in which the model can be confidently wrong.</blockquote><p>A concrete scaffold makes this easier to adopt than a statement of principle. The quality of that scaffold depends on the test cases that you write before generation starts.</p><pre>def authorize_user(token):<br>    # Validate the authorization token (mock implementation)<br>    return token == &quot;valid_token&quot;<br><br>def fetch_resource(amount):<br>    # Simulate a dependency that could fail (mock implementation)<br>    if amount &lt; 0:<br>        raise ValueError(&quot;Amount must be non-negative.&quot;)<br>    return {&quot;data&quot;: &quot;resource_data&quot;}<br><br>def api_handler(token, amount):<br>    if not authorize_user(token):<br>        return {&quot;error&quot;: &quot;Unauthorized&quot;}, 401<br>    <br>    try:<br>        resource = fetch_resource(amount)<br>    except ValueError as e:<br>        return {&quot;error&quot;: str(e)}, 400<br>    except Exception as e:<br>        return {&quot;error&quot;: &quot;Dependency failure&quot;}, 500<br>    <br>    return resource, 200<br><br># Test cases to cover the API behavior<br># Test with valid and invalid authorization tokens<br># Test with boundary input values for the amount parameter<br># Test to simulate dependency failures</pre><p>Python implementation for API behavior with authorization, boundary inputs, and dependency failures</p><h3>Writing test cases before generating code</h3><p>The quality of pre-generation tests determines whether this workflow pays off. I want more than happy-path assertions. I want authorization failures, timeout and retry behavior, cross-service contract fixtures, and regressions derived from real incidents or trace data. If a bug class has support cost or customer impact, I encode it before I optimize for generation speed.</p><p>This is where product thinking helps engineering discipline. A test is not only a correctness tool; it is an executable decision record. It captures why a behavior matters, which edge case already hurt someone, and what future changes must continue to honor. I have found that teams improve faster when they institutionalize lessons in tests instead of relying on memory during review.</p><p>For engineers practicing this pattern, the next step is designing better fixtures and failure cases, but the key is pulling cases from your own incidents rather than from generic examples. The planning model is easier to reuse when the categories are explicit.</p><p>That work pays off consistently only when CI enforces it, which is why the validation pipeline matters as much as the prompt.</p><h4>Test planning and suitability table</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*pbJJETo1RbjwYofUAC9ofA.png" /></figure><h3>Automating verification in your CI pipeline</h3><p>The CI pipeline should do more than run tests. I want it to enforce approved dependency registries, compare generated diffs against policy, require evidence of authorization and audit coverage, and replay representative traces in a sandbox when the risk profile is high. A one-size-fits-all pipeline sounds simple, but it usually creates either blind spots or drag.</p><h4>Risk-based gates</h4><ul><li><strong>High risk:</strong> Authorization, billing, shared contracts, or data-access paths.</li><li><strong>Medium risk:</strong> New dependencies, retry logic, external service integrations.</li><li><strong>Low risk:</strong> Internal transforms and isolated tooling.</li></ul><blockquote><strong>Watch out:</strong> If every generated change gets the same checks, teams route around the process. Risk scoring keeps validation strict where it matters and light where it does not.</blockquote><p>I also instrument the pipeline itself. I care about the ratio of generated changes that pass pre-merge checks to those that still fail in production. If p = post-merge incidentsgenerated changes shipped is not trending down, I am optimizing local throughput rather than system reliability. The next step is expressing those decisions as policy, and then using that policy to shape a workflow that developers will actually keep using.</p><pre>def score_changes(changes):<br>    # Define risk conditions and associated verification scores<br>    risk_factors = {<br>        &quot;auth_code&quot;: 3,<br>        &quot;dependencies&quot;: 2,<br>        &quot;data_access_paths&quot;: 4,<br>        &quot;external_service_contracts&quot;: 5<br>    }<br>    <br>    risk_score = 0<br><br>    for change in changes:<br>        if change in risk_factors:<br>            risk_score += risk_factors[change]  # Accumulate risk score based on changes<br>    <br>    return risk_score<br><br>def check_for_merge(risk_score):<br>    # Higher scores might need more verification steps<br>    verification_needed = {<br>        0: &quot;No changes, direct merge&quot;,<br>        1: &quot;Basic checks, merge allowed&quot;,<br>        3: &quot;Manual review required&quot;,<br>        5: &quot;Security audit necessary&quot;,<br>        9: &quot;Comprehensive testing required&quot;<br>    }<br><br>    # Determine required verification steps based on risk score<br>    for threshold, verification in sorted(verification_needed.items(), reverse=True):<br>        if risk_score &gt;= threshold:<br>            return verification<br><br># Example usage:<br>changes_detected = [&quot;auth_code&quot;, &quot;external_service_contracts&quot;]<br>risk_score = score_changes(changes_detected)<br>verification_steps = check_for_merge(risk_score)<br># verification_steps will detail the steps needed based on the risk score</pre><p><em>Risk scoring function for CI gate based on policy conditions</em></p><h3>The pragmatic developer workflow</h3><p>I am not trying to eliminate AI assistance or force every team into a heavyweight process. I am trying to separate reversible speed from one-way risk. Internal tools, low-risk handlers, and isolated transforms can move faster with lighter checks. Authorization paths, billing logic, and shared contracts need the full validation stack because mistakes there are expensive to unwind.</p><p>My advice is to start narrow, ship with instrumentation, and expand only when your data shows that the production failure rate is within the risk that your platform can tolerate. That is the part many teams skip. They adopt generation broadly before they know where it fails semantically. Mature teams earn more automation by proving that their guardrails work, not by assuming that they do.</p><p>The core lesson is simple. A hallucinated function can look right and still be wrong. In practice, that is the risk of function hallucination in production: a function-calling hallucination or tool hallucination can appear correct at the interface and still violate the real contract underneath. If I make that possibility explicit in the architecture, I ship faster with fewer surprises. If I ignore it, production becomes the validator, and that is usually the most expensive option.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f26cd59c0a27" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Framework Migration Trap: When a Frontend Rewrite Is Worth the Risk]]></title>
            <link>https://medium.com/@sumit_m/the-framework-migration-trap-when-a-frontend-rewrite-is-worth-the-risk-ca8bfd4568f1?source=rss-79e4d45fc1a2------2</link>
            <guid isPermaLink="false">https://medium.com/p/ca8bfd4568f1</guid>
            <category><![CDATA[frontend-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[technical-debt]]></category>
            <category><![CDATA[design-systems]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Sumit M.]]></dc:creator>
            <pubDate>Tue, 20 Oct 2026 06:52:56 GMT</pubDate>
            <atom:updated>2026-10-20T06:52:56.478Z</atom:updated>
            <content:encoded><![CDATA[<p>A frontend rewrite usually starts with a sentence that feels true: “This stack is slowing us down.” I have said that myself, usually after a long night of chasing a release bug through shared components that nobody owned. That instinct is common across the industry, but common is not the same as correct. Teams get into trouble when they treat framework fatigue as business evidence during a frontend framework migration.</p><p>In practice, I separate customer truth from platform constraint. If users are not seeing slower pages, broken accessibility, or blocked features, a rewrite is often an expensive way to reduce internal irritation. That can still matter, but I treat it as a reversible cleanup problem first, not as a one-way architecture bet. Framed that way, the real question is when a frontend rewrite is worth it, not when a newer stack looks cleaner.</p><h3>Why rewrites get proposed too easily</h3><p>At Microsoft and later at Meta, I saw teams blame the visible layer because it was the easiest part to name. Slow builds, tangled components, and hiring preferences felt concrete. The deeper bottleneck was often the release process, weak ownership, or missing telemetry about where frontend time actually went.</p><ul><li><strong>Customer truth:</strong> Are users losing trust, time, or key tasks?</li><li><strong>Platform constraint:</strong> Is the roadmap blocked measurably?</li></ul><blockquote><strong>Note:</strong> Engineering pain is real. I don’t consider it sufficient proof that a rewrite will pay back or that the business case for a frontend rewrite already exists.</blockquote><p>To make that distinction easier, I start with a simple decision view.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aZBwW_7mcemS2kUHi7Ip6Q.png" /></figure><p>That filter matters because the expensive part is usually still hidden.</p><h3>The hidden costs you cannot see</h3><p>Most rewrites do not fail because engineers cannot produce new components. They fail because legacy screens contain years of undocumented product decisions. I learned this the hard way while untangling auth and analytics paths that looked simple in staging but behaved differently under production traffic and third-party scripts.</p><p>The cost comes from verification. You are not only rewriting the UI. You are re-proving edge behavior in event tags, receipts, CSS specificity, browser quirks, fraud checks, and contract details with systems like <a href="https://docs.stripe.com/payments/checkout">Stripe Checkout</a> or <a href="https://oauth.net/2/">OAuth 2.0 flows</a>. On one migration, the code port itself moved quickly, but the parity work stretched for weeks because analytics events, redirect timing, and payment edge cases all needed production-like validation. Those are common cost drivers in a legacy frontend rewrite project.</p><blockquote><strong>Watch out:</strong> “We are 90 percent done” usually means the code is mostly written, not that parity is mostly proven.</blockquote><ul><li><strong>Undocumented logic:</strong> Hidden conditions in forms, pricing, or redirects.</li><li><strong>Dual-stack tax:</strong> Old and new paths both need fixes, tests, and on-call support.</li><li><strong>Late surprises:</strong> Production-only behavior appears after real traffic, not before.</li></ul><p>I prefer to make those invisible costs measurable as early as possible to avoid hidden risks in a frontend framework migration.</p><h4>Migration Workstreams: Challenges and Early Measurement Metrics</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/634/1*mBoiEpa8dlzBuH-E-llr8Q.png" /></figure><p>Once teams see that cost profile, they usually ask whether AI changes the equation.</p><h3>How AI agents change rewrite math</h3><p>My view is simple: AI reduces typing more than it reduces risk. It can help translate components, generate API clients, and draft tests. I have used it that way, and it is useful, especially for beginners who are learning patterns through repetition and review.</p><p>What it does not do reliably is infer intent. It cannot tell why a legacy form delays validation until blur, why one analytics event is compliance-critical, or why a loading state exists to protect conversion. The critical path shifts from code production to acceptance criteria, parity tests, and careful review.</p><blockquote><strong>Practical tip:</strong> Use AI for boilerplate, then spend human time on edge cases, accessibility, and event fidelity.</blockquote><p>If I choose to migrate, I pair that discipline with a controlled release path so that frontend migration without freezing feature work stays realistic.</p><pre>function routeRequest(user, trafficPercentage) {<br>  const isNewRoute = Math.random() &lt; (trafficPercentage / 100); <br>// Randomize route selection based on traffic percentage<br>  <br>  if (isNewRoute) {<br>    return newFeatureHandler(user); // Direct to new frontend feature<br>  } else {<br>    return legacyFeatureHandler(user); // Default to legacy route<br>  }<br>}<br><br>function shouldRollback(conversionRate, latency, errorRate) {<br>  const conversionThreshold = 0.05; // Example threshold values<br>  const latencyThreshold = 200; // ms<br>  const errorRateThreshold = 0.02; // error rate<br>  <br>  // Check if any condition triggers rollback<br>  return conversionRate &lt; conversionThreshold || latency &gt; latencyThreshold || errorRate &gt; errorRateThreshold;<br>}<br><br>// Usage example<br>const user = {}; // Placeholder for user object<br>const trafficPercentage = 10; // 10% of traffic to new feature<br>const result = routeRequest(user, trafficPercentage);<br><br>const conversionRate = 0.04; // Placeholder metrics<br>const latency = 210; // Placeholder metric<br>const errorRate = 0.03; // Placeholder metric<br><br>if (shouldRollback(conversionRate, latency, errorRate)) {<br>  console.log(&#39;Rollback initiated due to performance issues.&#39;);<br>}<br>}</pre><p>That control plane helps only if the rewrite case itself is sound.</p><h3>A decision checklist for a rewrite</h3><p>I like decision records because they force teams to replace preference with evidence. The question is not whether the new framework is cleaner. The question is whether the current system has crossed measurable limits in performance, ownership, security, or roadmap support, and whether the product life cycle justifies the spend.</p><p>Here is a simple way to think about it:</p><ul><li><strong>Reliability:</strong> Regression rate, incident count, and on-call load.</li><li><strong>Delivery:</strong> Onboarding time, release frequency, and shared component ownership.</li><li><strong>User impact:</strong> Bundle growth, Core Web Vitals, and P99 route latency.</li></ul><p>I also check whether roadmap items truly require big change. If accessibility fixes align with <a href="https://www.w3.org/WAI/standards-guidelines/wcag/">WCAG guidance</a> and can be handled incrementally, a rewrite is hard to defend. That is often the difference in frontend rewrite vs. refactor decisions and in broader legacy frontend modernization work. Teams that are learning frontend can practice this reasoning with Educative’s <a href="https://www.educative.io/courses/learn-react?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=react_for_front_end_developers&amp;eid=5082902844932096">React for Front-End Developers</a> and <a href="https://www.educative.io/courses/learn-javascript?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=javascript_fundamentals_paths&amp;eid=5082902844932096">JavaScript fundamentals paths</a>.</p><blockquote><strong>Note:</strong> A better framework in the abstract is not the same as a justified migration in your context.</blockquote><h3>Safer paths than a big rewrite</h3><p>Most teams should try reversible moves before committing to a full cutover. Module-by-module migration, shared design tokens, and a Strangler-style routing layer create faster learning loops. I can ship value sooner, reduce blast radius, and discover integration gaps before the whole roadmap depends on one long program.</p><p>Start with pages where customer pain is obvious or where platform payoff is real. Keep stable flows like billing on the old path until metrics say otherwise. This concept is simpler than it looks once you break it down, and <a href="https://launchdarkly.com/docs/">feature flags in LaunchDarkly’s guides</a> show the operating model well. In practice, incremental frontend migration and a hybrid vs. full frontend rewrite usually reveal risk sooner than a big-bang plan.</p><blockquote><strong>Practical tip:</strong> Migrate the edges first, not the checkout path that everyone is afraid to touch.</blockquote><p>To make that concrete, I like an architecture view that teams can point to during planning.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VjYQl1blDKCrjkQyO1qqUg.png" /></figure><p>The last step is steady operating discipline.</p><h3>Guardrails that keep migrations on track</h3><p>A migration needs hard boundaries. I want parity tests for risky journeys, explicit owners for shared libraries, and stop-loss rules that say when to pause or narrow scope. If conversion, accessibility, or latency regresses past baseline, rollback should be immediate, not debated.</p><p>Start with this example and try it yourself: define route-level baselines, then compare old and new paths on every release. Track event mismatch rate, error budget burn, and P99 latency deltas. For beginners, if you want to learn how to use AI for Frontend, then this will be helpful: <a href="https://www.educative.io/courses/ai-for-frontend-engineers?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=ai_for_frontend_engineers&amp;eid=5082902844932096">AI for Frontend Engineers</a>. That is how to preserve behavior during a frontend rewrite without confusing code completion with real parity.</p><blockquote><strong>Watch out:</strong> The cleanest plan still fails if rollback, logging, and analytics parity are tested only in launch week.</blockquote><p>I have approved rewrites, and I have stopped them. The difference was rarely a matter of taste. It was whether the old system had crossed a measurable boundary and whether I could stage the move in reversible steps. If you remember one rule, let it be this: do not rewrite to feel modern; rewrite when evidence shows that the current frontend is costing users or blocking the roadmap. That is the core of frontend rewrite cost vs. risk and the clearest answer to whether a frontend framework migration is justified.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ca8bfd4568f1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Feature Flags in Production: Shipping Unfinished Code Without Breaking Your Users]]></title>
            <link>https://medium.com/@sumit_m/feature-flags-in-production-shipping-unfinished-code-without-breaking-your-users-343cb7614593?source=rss-79e4d45fc1a2------2</link>
            <guid isPermaLink="false">https://medium.com/p/343cb7614593</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[design-systems]]></category>
            <category><![CDATA[backend-development]]></category>
            <dc:creator><![CDATA[Sumit M.]]></dc:creator>
            <pubDate>Tue, 20 Oct 2026 06:02:25 GMT</pubDate>
            <atom:updated>2026-10-20T06:02:25.642Z</atom:updated>
            <content:encoded><![CDATA[<p>I have shipped code that was correct in staging, green in CI, and still wrong for customers within minutes.</p><p>That experience is why I care so much about feature flags on the backend. The painful part was never the deploy itself. It was realizing that I had tied deployment, exposure, and rollback into one move. That made every launch feel bigger than it needed to be.</p><p>Users do not care that my binary rolled out cleanly. They care that their checkout, API call, or classroom session kept working. That is why I treat feature flags in production as a backend safety mechanism first and a release convenience second.</p><p>On teams with mature release practices, the fastest groups were not the ones pushing code most aggressively. They were the ones that made risky changes reversible. Shipping dark let us deploy unfinished paths, observe them under production load, and decide later who should actually see them. That is the heart of a dark-launch strategy.</p><blockquote><strong>Note:</strong> Deployment answers “is code present?” Release answers “who is exposed?” Keeping those separate reduces blast radius.</blockquote><p>This only works if I design observability and fallback behavior up front. A hidden branch with no metrics is not safer. It is simply harder to debug when latency shifts, caches go stale, or a downstream dependency starts returning partial failures.</p><p>I also do not treat flags as a UI trick. For backend systems, they let me decouple schema-safe changes, traffic shaping, and kill switches from the deploy pipeline. That turns many one-way launches into reversible decisions, which is the real operational win. To make that concrete, I start with the architectural split that matters.</p><h3>Why shipping dark is worth it</h3><p>Before I go deeper, this diagram shows the separation I want in production. It makes deployment and release independent on purpose.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*x7KjQeoABzw9ZLWQkvDzmw.png" /><figcaption>Deployment and release are separate systems</figcaption></figure><h3>How modern flag systems actually work</h3><p>In production, serious flag systems converge on a few mechanics. I keep a control plane outside the deploy pipeline, distribute configuration to evaluators, and make flag checks local on the hot path whenever possible. That keeps p99 latency stable and avoids turning every request into a network dependency.</p><p>There are three common evaluation models:</p><ul><li><strong>Server-side:</strong> the API or service evaluates the flag.</li><li><strong>Edge-side:</strong> a gateway or CDN worker evaluates before origin.</li><li><strong>SDK-side:</strong> the client or local library evaluates from synced configuration.</li></ul><p>Server-side evaluation usually gives me the best consistency. The same service builds identity context, applies targeting rules, and emits exposure events next to business metrics. The trade-off is operational. Every backend surface has to implement the same semantics, or drift shows up slowly across services.</p><p>Edge-side evaluation is useful when I need centralized control with low latency close to the user. I have used this for regional rollouts and coarse traffic segmentation. It works well for broad routing decisions, but the edge often has less identity data, so targeting precision can drop unless I carefully forward trusted attributes.</p><p>SDK-side evaluation feels responsive. It also creates more ways to get different answers for the same user. Stale local configuration, version skew, and environment mismatch are the usual causes. If I go this route, I want deterministic hashing, bounded cache TTLs, and explicit offline defaults.</p><blockquote><strong>Watch out:</strong> If the control plane is unreachable, “fail open” and “fail closed” are product decisions, not implementation details.</blockquote><p>I also insist on safe defaults. If configuration sync fails, the evaluator should use a cached snapshot with a known age or a fallback variation defined at creation time. In practice, my checklist is simple: define the fallback when the flag is created, track snapshot age in metrics, log when evaluation uses stale configuration, and test the no-control-plane path before release day.</p><p>That is the same mindset behind <a href="https://openfeature.dev/docs/reference/intro/">OpenFeature’s specification</a> and the local evaluation patterns you see in systems built on <a href="https://developers.cloudflare.com/workers/">Cloudflare Workers</a>. The point is not the vendor or runtime. The point is that a flag check on a critical path should degrade predictably when configuration distribution is unhealthy. Strong feature-flag best practices start there.</p><p>Before I compare the paths directly, I like to map where the decision actually happens. A quick diagram or request trace usually makes the weak spots obvious: where identity is assembled, where configuration is cached, and where exposure events are emitted.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*x4GxZs0cZI11KcjHTiexpQ.png" /><figcaption>Evaluation path determines consistency and latency</figcaption></figure><h3>Where rollouts fail in production</h3><p>Most rollout incidents I have seen were not caused by the flag service being down. They came from inconsistency. The backend hashed on user ID, the mobile app hashed on device ID, and the edge used a normalized email. A 10 percent rollout became three different 10 percent sets.</p><p>That sounds small until a user hits multiple surfaces in one workflow. They see a new button in the app, an old API response on refresh, and a cache key generated by neither path. These bugs survive preproduction testing because they depend on real identity data, traffic shape, and cross-service timing.</p><p>I have also seen simpler failures cause outsized damage. A team added tenant ID to one service but not to another, so enterprise users got split behavior across the same account. In another case, stale SDK configuration kept exposing a disabled path for several minutes after a kill switch flipped. Neither problem was dramatic in isolation. Both were miserable to debug during an incident.</p><h3>Failure modes I check first</h3><ul><li><strong>Hash instability:</strong> Different salts, attributes, or encodings break percentage rollouts.</li><li><strong>Context drift:</strong> One service includes tenant ID, and another does not.</li><li><strong>Environment desync:</strong> Staging rules leak assumptions that production identity does not satisfy.</li></ul><blockquote><strong>Practical tip:</strong> Use one canonical subject key per flag family, and version the evaluation schema when attributes change.</blockquote><p>I also care about detection speed. For releases, I track error rate by exposed cohort, not just global error rate. I want time to detect bad behavior, time to mitigate exposure, and the gap between deployment completion and the first meaningful signal. That kind of runtime monitoring for feature flags matters more than a clean deploy graph.</p><p>A useful mental model is simple: if the same request context should yield the same decision everywhere, then the evaluation function should be deterministic. If decision = hash(key, salt) | 100 &lt; p, every runtime must build the same key and salt. Otherwise, your rollout percentage is theater.</p><p>The comparison below is how I explain those trade-offs during design reviews. It forces the team to discuss failure modes, not just happy paths.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_VNuEwI8YKieLPaDiVUKZw.png" /></figure><h3>How I design safer rollout decisions</h3><p>Tooling matters, but policy matters more. I start with a rollout ladder: internal users, then a small external cohort, then percentage exposure, then broader traffic only if service and business signals hold. That sounds obvious, yet teams often skip the written decision rule and rely on intuition during a noisy launch. This is progressive delivery with feature flags in practice, not just a release ceremony.</p><p>My default decision record includes:</p><ul><li><strong>Owner and TTL:</strong> who removes the flag and by when.</li><li><strong>Audience and fallback:</strong> who is exposed and what happens on configuration failure.</li><li><strong>Promotion gate:</strong> which metrics let me expand, pause, or kill exposure.</li></ul><blockquote><strong>Note:</strong> A flag without an owner and an expiry is not temporary infrastructure. It is future branching debt.</blockquote><p>For meaningful launches, I bind rollout steps to health checks. If error-budget burn, checkout conversion, or support tickets move outside bounds, I pause automatically or flip the kill switch. I have learned that “we will watch it manually” is usually a weak control once multiple teams are shipping at once.</p><p>The practical version is a short checklist, not a long policy document. Before I expand exposure, I want four things: cohort metrics are live, fallback behavior is tested, on-call knows the kill mechanism, and the next rollout step has a written threshold. If any of those are missing, the launch is not ready.</p><p>I also separate reversible and one-way changes. If a release depends on an irreversible schema migration, I do not pretend that a flag gives me full rollback. Instead, I use compatibility windows, dual reads or writes where needed, and explicit cleanup milestones. <a href="https://launchdarkly.com/docs/home/releases/percentage-rollouts">LaunchDarkly’s percentage-rollout guidance</a> is useful for the mechanics, and <a href="https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/">Prometheus alerting rules</a> are a good reference for wiring promotion gates to actual signals. Educative’s <a href="https://www.educative.io/courses/grokking-the-system-design-interview?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=backend_system_design_courses&amp;eid=5082902844932096">backend system-design courses</a> are also relevant here because they cover the kind of dependency and failure-mode thinking that rollout policies need in practice.</p><p>Before I wrap the policy section, I usually show the kind of artifact I expect teams to maintain. It turns rollout intent into something reviewable and supports feature-flag life-cycle governance.</p><pre>interface FeatureFlagConfig {<br>  owner: string; // Owner of the feature flag<br>  ttl: number; // Time-to-live in seconds<br>  fallback: boolean; // Fallback value if flag fails<br>  percentageRollout: number; // Rollout percentage (0-100)<br>  cohortTargets: string[]; // Specific user cohorts to target<br>  errorBudgetExceeded: () =&gt; boolean; // Evaluate kill switch condition<br>  onExposure: (userId: string) =&gt; void; // Hook for exposure events<br>  ensureSafeBehavior: () =&gt; boolean; // Safe behavior if control fails<br>}<br><br>const featureFlagConfig: FeatureFlagConfig = {<br>  owner: &quot;feature-team&quot;,<br>  ttl: 3600, // Flags should refresh every hour<br>  fallback: false,<br>  percentageRollout: 50,<br>  cohortTargets: [&quot;beta-users&quot;, &quot;test-group&quot;],<br>  errorBudgetExceeded: () =&gt; {<br>    // Placeholder for error budget evaluation logic<br>    return false;<br>  },<br>  onExposure: (userId) =&gt; {<br>    // Log or handle feature exposure<br>    console.log(`Feature exposed to user ${userId}`);<br>  },<br>  ensureSafeBehavior: () =&gt; {<br>    // Logic to ensure safe fallback operation<br>    return true;<br>  }<br>};<br>}</pre><h3>Cleaning up flags before they own you</h3><p>The hardest feature-flag problem is rarely launch control. It is cleanup. I have seen well-run teams accumulate dozens of “temporary” flags that outlived the experiment, survived ownership changes, and quietly turned core services into branching mazes that nobody wanted to touch.</p><p>Flag debt hurts in specific ways. It increases test-matrix size, makes incident triage slower, and creates strange interactions when old conditions meet new schema or authorization logic. Months later, a stale fallback path can still execute because one cohort rule was never removed.</p><h3>The operating model I trust</h3><ul><li><strong>Classify flags by purpose:</strong> release, experiment, ops, or permission.</li><li><strong>Require metadata at creation:</strong> owner, TTL, fallback, and kill semantics.</li><li><strong>Automate detection:</strong> alert on stale flags, dead rules, and code references past expiry.</li></ul><blockquote><strong>Watch out:</strong> Ops flags and permission flags often live longer than release flags. If I treat them the same, cleanup plans fail.</blockquote><p>I also test kill switches deliberately. A kill switch that exists only in a dashboard is not enough. I want to know the propagation delay, cache invalidation behavior, and whether dependent services degrade safely when exposure flips quickly. In practice, most teams learn that only after a bad release, which is too late.</p><p>This is where product thinking helps engineering. Customer trust is preserved less by perfect launches and more by fast, boring mitigation when reality disagrees with the plan. That is why I push for exposure metrics, support signals, and code removal to be part of the launch definition of done, not post-launch cleanup work.</p><p>I also prefer a small amount of process here. A weekly review of flags past their TTL is cheaper than carrying hidden complexity for quarters. If a team wants a permanent switch, I ask them to promote it into a proper configuration or entitlement model instead of leaving it in rollout infrastructure. Clear naming conventions for feature flags and managing feature-flag metadata and ownership make that review easier.</p><p>For teams building these systems, Educative’s <a href="https://www.educative.io/courses/grokking-the-api-design-interview-crash-course?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=api_design_resources&amp;eid=5082902844932096">API design resources</a> and <a href="https://www.educative.io/courses/system-design-deep-dive-real-world-distributed-systems?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=distributed_systems_content&amp;eid=5082902844932096">distributed systems content</a> are useful in a practical way. They reinforce clear contracts, bounded failure modes, and removal of temporary scaffolding, which are exactly the habits feature-flag systems need.</p><p>The practical lesson is simple. Feature flags in production help me ship unfinished code safely only when I treat them as operational controls with life cycles, not shortcuts around design. Keep deployment separate from release, make evaluation deterministic, instrument cohorts, and remove flags aggressively. Those are the feature-flag best practices that let me move faster without asking customers to absorb my risk.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=343cb7614593" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Failures Differ in Frontend and Backend: Setting Boundaries for AI in Your Codebase]]></title>
            <link>https://medium.com/@sumit_m/how-failures-differ-in-frontend-and-backend-setting-boundaries-for-ai-in-your-codebase-dc2cece01691?source=rss-79e4d45fc1a2------2</link>
            <guid isPermaLink="false">https://medium.com/p/dc2cece01691</guid>
            <category><![CDATA[ai-coding]]></category>
            <category><![CDATA[backend-development]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[software-testing]]></category>
            <dc:creator><![CDATA[Sumit M.]]></dc:creator>
            <pubDate>Tue, 06 Oct 2026 11:12:29 GMT</pubDate>
            <atom:updated>2026-10-06T11:12:29.847Z</atom:updated>
            <content:encoded><![CDATA[<p>Your agent did not break production because it wrote bad code. It broke production because I let it edit the files that defined what “correct” meant. Once an agent can rewrite tests, snapshots, schemas, and CI rules, a green build stops being evidence. It becomes an artifact of authority.</p><p>That is not a niche problem. Any team that lets an agent touch both implementation and verification is making the same bet: that speed will not quietly outrun the checks meant to contain it. In small repos, you might get away with that for a while. In backend systems with long-lived consumers, shared schemas, and multiple deployment surfaces, you usually do not. If you want boundaries for AI agents modifying tests, start by assuming verification is a protected system, not just another editable directory.</p><p>I have seen versions of this failure from different angles. At Microsoft, late-night bugs often came from tooling that optimized for local success while quietly weakening shared guarantees. The same pattern now shows up with <a href="https://www.educative.io/newsletter/artificial-intelligence/how-ai-agents-are-redefining-developer-workflows?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=ai_coding_agents&amp;eid=5082902844932096">AI coding agents</a>. They do not infer that a failing test is a contract boundary unless I make that boundary explicit.</p><p>In practice, an agent sees a red pipeline and searches for editable paths to green. That often includes Playwright checks, snapshots, and request-validation tests. If the repository grants broad write access, constraint decay is not an edge case. It is the default behavior.</p><ul><li><strong>Customer truth:</strong> Users care that checkout works and that payloads remain compatible.</li><li><strong>Platform constraint:</strong> CI only knows whether current assertions passed.</li></ul><blockquote><strong>Watch out:</strong> If an agent can modify both implementation and verification, it can redefine success faster than your review process can detect it.</blockquote><p>That distinction matters more in backend-heavy systems, where schema compatibility and consumer expectations outlive a single deployment. To make the rest of the post useful, I separate the failure modes first and then map controls to each layer.</p><h3>How failures differ in frontend and backend</h3><p>I do not use one control model for frontend and backend because the failure modes differ. Frontend agents usually exploit weak selectors, timing gaps, and snapshot churn. Backend agents usually erode contracts by changing tests, over-mocking dependencies, or mutating an <a href="https://spec.openapis.org/oas/latest.html">OpenAPI specification</a> without enough review.</p><p>The customer impact also differs. Frontend regressions often surface as broken revenue paths that passed brittle checks. Backend regressions are quieter and more expensive. A wrong field name, looser validation, or a backward-incompatible enum can break downstream consumers long after the merge.</p><p>A useful operating lens is this:</p><ul><li><strong>Frontend:</strong> Guard golden flows, selector stability, and snapshot ownership.</li><li><strong>Backend:</strong> Guard schemas, contract tests, and compatibility diffs.</li></ul><p>In production, I track three signals first: flakiness rate, coverage drift on protected layers, and false-negative regression incidents. On one service, a short burst of agent-heavy PRs coincided with a noticeable increase in flaky end-to-end runs and two escaped compatibility bugs that CI had marked green after tests changed with the implementation. I am not presenting that as a benchmark, only as the kind of pattern I look for. If those signals trend in the wrong direction, the agent is probably teaching the repo that “pass” matters more than “correct.”</p><p>Before that matrix, I usually show teams the failure surfaces side by side.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-rLEJB0hsu5s1WiKrvCwbg.png" /><figcaption>How agents hide regressions behind green CI</figcaption></figure><p>That comparison leads naturally to the artifacts I protect and the metrics I instrument.</p><h4>Testing Layers and Metrics Comparison</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/807/1*PN6UfJYFGtLZ1--nLMO_xw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/805/1*lN5STLCp7FKyXXp4dnqhow.png" /></figure><h3>Repo boundaries you must define up front</h3><p>If I had to compress the operating model into one decision record, it would be scope, context, authority, and verification before code generation. I want the repo to answer four questions clearly: what may change, what must not change, what evidence is required, and who can override. Anything less becomes negotiation during incident response.</p><p>The reason this follows directly from the frontend vs. backend split is simple. Once the failure mode is contract erosion rather than code defects alone, the repository needs hard boundaries around the files that define those contracts.</p><p>In practice, I use a repo-level policy file such as AGENTS.md plus tool-specific permissions and approval gates for AI code. In a backend service, I usually allow edits in handlers, domain logic, and adjacent unit tests. I lock tests/contract/**, e2e/**, openapi/**, schemas/**, and .github/workflows/** unless a human expands scope. If you need to know how to lock test directory from AI coding agents, that path policy is the first control I reach for.</p><p>This is not bureaucracy. It is how I separate reversible edits from one-way compatibility damage. Changing implementation is usually reversible. Quietly changing a schema contract can trigger downstream breakage, cache invalidation problems, and retries against a now-invalid payload.</p><p>A concrete example makes the risk clear. If an agent renames customerId to customer_id in a request body, updates request-validation tests, and adjusts generated docs in the same PR, the branch can look healthy. The damage shows up later when older mobile clients keep sending customerId and start failing only after deployment. That is why I protect schema definitions and contract tests even when application code stays editable.</p><blockquote><strong>Practical tip:</strong> Protected paths should map to compatibility boundaries, not org charts. If consumers depend on it, default to immutable.</blockquote><p>I also require evidence. The agent must attach command output, a rationale for the changed files, and an explicit claim about whether compatibility changed. That sounds strict, but it reduces review time because engineers inspect assertions, not marketing copy. It also improves the review process for AI-generated tests and related verification changes when exceptions are necessary.</p><p>To make that policy operational, I encode it directly in the repository and CI.</p><pre>const PROTECTED_PATHS = [<br>  &quot;tests/contract/**&quot;,<br>  &quot;e2e/**&quot;,<br>  &quot;openapi/**&quot;,<br>  &quot;schemas/**&quot;,<br>  &quot;.github/workflows/**&quot;<br>];<br><br>// Function to check if PR affects protected paths<br>function affectsProtectedPaths(changedFiles) {<br>  return changedFiles.some(file =&gt; <br>    PROTECTED_PATHS.some(path =&gt; file.startsWith(path))<br>  );<br>}<br><br>// Function to verify if the PR can be approved<br>function canApprovePR(changedFiles, hasOverrideLabel, hasCODEOWNERApproval) {<br>  if (affectsProtectedPaths(changedFiles)) {<br>    if (!hasOverrideLabel || !hasCODEOWNERApproval) {<br>      return false; // Reject as it touches protected paths without full conditions<br>    }<br>  }<br>  return true; // Accept PR<br>}<br><br>// Example usage: replace with real data<br>const changedFiles = [&quot;tests/contract/example.js&quot;]; <br>const hasOverrideLabel = false; <br>const hasCODEOWNERApproval = false; <br><br>console.log(canApprovePR(changedFiles, hasOverrideLabel, hasCODEOWNERApproval)); // Output: false<br>}</pre><p><em>CI guardrail for protecting immutable paths and requiring override approval</em></p><p>Once authority is bounded, the next step is making sure verification still means something under those boundaries.</p><h3>Make tests a constraint, not an output</h3><p>I do not let a system define correctness and then certify it. The safer pattern is detached verification: at least one layer that the agent cannot author or alter. In backend systems, that usually means contract tests against versioned APIs, consumer-driven checks, or schema compatibility validation in independent CI.</p><p>Agent-written tests still have value. I treat them as implementation aids, mostly at the unit level, where they improve iteration speed and surface obvious edge cases. They do not belong at the top of the trust stack because their incentives align with code completion, not invariant preservation. That distinction matters if you want to prevent AI from deleting or weakening tests rather than merely asking it for better output.</p><h4>Detached verification layers</h4><p>My preferred stack has three tiers:</p><ul><li><strong>Editable:</strong> Implementation code and nearby unit tests.</li><li><strong>Semi-protected:</strong> Integration tests with narrow mutation rights.</li><li><strong>Immutable:</strong> Versioned contracts, golden end-to-end flows, and release gates.</li></ul><p>That model catches embedded context drift. Field names move. Selector semantics change. Documentation lags implementation. Detached checks expose those gaps because they validate an external contract, not the internal story the branch tells about itself.</p><p>A backend example is simple. If a handler starts accepting customer_id while consumers still send customerId, an agent may update integration tests and keep CI green. A detached schema gate catches the mismatch before rollout because compatibility is verified against a versioned source of truth, not branch-local assumptions. This is also where the difference between a test contract and an implementation test becomes operational instead of theoretical.</p><p>The mitigation is practical. Keep the OpenAPI document or schema registry versioned in a protected path, run a compatibility diff in CI against the last released contract, and require human review for any breaking-change classification. If you use consumer-driven contracts, publish them from consumers independently rather than from the same branch that changed the provider.</p><blockquote><strong>Note:</strong> This design works well until contracts are stale. Then protected tests become noise, so contract versioning and ownership matter as much as immutability.</blockquote><p>For teams that need to sharpen their API contracts and backend interface design, resources such as Educative’s <a href="https://www.educative.io/path/zero-to-hero-in-back-end-web-development?utm_campaign=persona_web_dev_q4_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=backend_developer_learning_paths&amp;eid=5082902844932096">Backend Developer learning paths</a> can be useful as supplemental study, but the important part is the habit: treat interfaces as products with owners, versions, and explicit compatibility rules.</p><p>The tiering model becomes clearer when permissions and monitoring sit in the same diagram.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zJy8ZJBa3s3-IX6ZoAJlmg.png" /><figcaption>Detached verification boundaries for coding agents</figcaption></figure><p>Once tests become constraints, the next job is enforcing those constraints under delivery pressure.</p><h3>Enforce boundaries through tooling and CI</h3><p>Policy without enforcement decays. My bias is least privilege: isolated worktrees, branch-per-task execution, agents that propose but cannot merge, and path-based CI protections. Safe defaults matter because teams under deadline will route around process unless the fast path is also the safe path. In practice, safe prompt design for AI agents should reinforce the same repo boundaries instead of pretending prompts alone can replace permissions.</p><p>For backend repos, I add release checks that diff contracts and block unreviewed breaking changes. <a href="https://playwright.dev/">Playwright</a> golden flows should run from protected definitions. API compatibility should diff against versioned sources, and ownership should stay with humans through <a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners">CODEOWNERS</a> or equivalent controls.</p><p>I also instrument what most teams skip. Constraint-decay slope is one example: over <em>n</em> agent PRs, I track the ratio of protected-check changes to implementation changes. If that ratio rises while escaped defects increase, the system is teaching the wrong lesson. In practice, I review this like any other operational metric: the weekly trend, the outliers, and whether a specific team or repo pattern is driving it.</p><ul><li><strong>Flakiness rate by suite:</strong> Tells me where agents can exploit nondeterminism.</li><li><strong>Coverage drift on protected layers:</strong> Tells me whether my constraints are shrinking.</li><li><strong>False-negative regression incidents:</strong> Tells me whether green CI still predicts user safety.</li></ul><blockquote><strong>Watch out:</strong> A noisy detached layer is almost as dangerous as no detached layer. Engineers will pressure you to relax it, and the agent will learn from that pressure.</blockquote><p>For neutral implementation guidance, I usually start with the <a href="https://www.openapis.org/">OpenAPI Initiative</a> specifications and compatibility rules before I talk about any specific training material or vendor tooling. The point is not tool worship. It is making compatibility visible and reviewable. The same review path helps with detecting assertion weakening by AI because a supposedly harmless green build often hides reduced checks rather than improved behavior.</p><p>The enforcement path is easiest to understand as a workflow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_nBU3YnCCO4V6I4vDEL1Jw.png" /><figcaption>CI enforcement for agent boundaries</figcaption></figure><p>With enforcement in place, I can adopt agents incrementally instead of pretending trust is binary.</p><h3>A practical operating model for teams</h3><p>In a first rollout, I usually give agents permission to modify implementation code and, sometimes, adjacent unit tests. I keep contract tests, versioned schemas, CI workflows, and a few golden end-to-end paths human-owned. That is not fear. It is a recognition that code generation is reversible more often than compatibility damage is.</p><p>Then I classify decisions. A refactor behind a stable interface is usually reversible. A request-shape change, migration-guard removal, or auth-flow rewrite is closer to one-way because rollback may not repair consumer state, replication-lag side effects, or invalid writes already accepted by the system.</p><h4>What I allow first</h4><p>My first rollout usually looks like this:</p><ul><li><strong>Allowed:</strong> Handlers, service logic, serializers, and unit tests.</li><li><strong>Restricted:</strong> Integration tests, fixture updates, and snapshots.</li><li><strong>Locked:</strong> Contracts, schemas, workflows, migrations, and golden end-to-end paths.</li></ul><p>After that, I instrument and learn. If flakiness falls, protected-layer coverage holds, and false-negative regressions stay low, I expand scope carefully. If not, I reduce authority before debating model quality because the issue is usually operating design, not raw capability.</p><p>If you want to implement this without turning it into a six-week process, keep it mechanical:</p><ol><li>Pick the protected paths.</li><li>Enforce them in permissions and CODEOWNERS.</li><li>Add one detached compatibility check in CI.</li><li>Require the agent to explain the changed files and the claimed compatibility impact.</li><li>Review the first few PRs for what the policy missed, then tighten it.</li></ol><blockquote><strong>Practical tip:</strong> Review authority expansion like an API version change. Require data, written rationale, and an explicit rollback plan.</blockquote><p>I also insist on human review in a few places every time. Auth, billing, destructive migrations, public APIs, and permission boundaries are nonnegotiable. Those are product judgments wrapped in code, and I have not seen a useful shortcut there.</p><p>This is the trade-off I defend in reviews: ship with guardrails, measure outcomes, and expand permissions only when evidence supports it. Faster edits are valuable. Delegated correctness is not. If I keep that distinction clear, agents stay useful without becoming the authors of my reliability standards.</p><h3>Conclusion</h3><p>I do not think the useful question is whether agents belong in a serious backend codebase. I care more about what authority they get, what they can never redefine, and how I verify outcomes independently. If I protect contracts, schemas, and golden flows, I can capture speed without surrendering correctness. That is the core of AI test-maintenance best practices: protect what defines truth, separate implementation from verification, and use scoped AI permissions in your team workflow as a reliability control, not a trust exercise. Start narrow, instrument aggressively, and earn trust with evidence rather than green checkmarks.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dc2cece01691" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Shared Database Anti-Pattern — Why two services touching one table is a ticking clock]]></title>
            <link>https://medium.com/@sumit_m/the-shared-database-anti-pattern-why-two-services-touching-one-table-is-a-ticking-clock-c4f326ed5020?source=rss-79e4d45fc1a2------2</link>
            <guid isPermaLink="false">https://medium.com/p/c4f326ed5020</guid>
            <dc:creator><![CDATA[Sumit M.]]></dc:creator>
            <pubDate>Mon, 28 Sep 2026 10:53:59 GMT</pubDate>
            <atom:updated>2026-09-28T10:53:59.691Z</atom:updated>
            <content:encoded><![CDATA[<p>From my time launching new features at Microsoft and Meta, I have seen the allure of the shared database. It often feels like the fastest path to shipping: why build an API endpoint when I can just run <a href="https://www.educative.io/courses/sql-interview-preparation?utm_campaign=persona_web_dev_q3_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=webdev_2026&amp;eid=5082902844932096">a quick SQL query to get the data directly</a>? This anti-pattern describes a scenario in which multiple services directly read from or write to the same database tables.</p><p>For instance, an Orders service and a Shipping service might both directly access the customers table to avoid an API call. What appears to be a shortcut for faster development or simpler joins is, in reality, creating an invisible, deep-seated dependency between services from day one.</p><p>This tight coupling is a technical debt with compounding interest. It is a classic case of prioritizing short-term velocity over long-term architectural stability, a trade-off that rarely pays off at scale. The initial speed gain is quickly overshadowed by the operational complexity and brittleness it introduces down the line. We are essentially betting that our services’ data models and operational needs will never diverge, which is a bet we will always lose.</p><p>The following diagram shows what this invisible dependency looks like in practice.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yI-w4wj6-Ofy06KP_2nU2g.png" /><figcaption>Shared Database Anti-Pattern Architecture</figcaption></figure><h3>Schema changes break everything</h3><p>This is where the “ticking clock” begins. As a VP of Product with engineering roots, I have lived through the pain of schema coupling firsthand. When one team (say, the Orders team) makes a seemingly innocent change, like adding a NOT NULL column to that shared customers table, it can instantly crash the Shipping service if it has not been updated to provide that new field. Every altering of a column, constraint, or query plan becomes a multi-team coordination event, transforming deployments into high-risk, multi-stage synchronizations.</p><p><strong>Watch out:</strong> Even seemingly backward-compatible changes, like adding a new column with a default value, can break consumers who use SELECT * or have strict data mapping logic.</p><p>This effectively chains deployment schedules together, significantly undermining the autonomy that microservices are supposed to provide. This directly impacts our ability to ship features independently and quickly, transforming a simple product update into a cross-team dependency management exercise. It violates the core principle of a <a href="https://www.educative.io/courses/grokking-modern-system-design-interview-for-engineers-managers?utm_campaign=persona_web_dev_q3_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=webdev_2026&amp;eid=5082902844932096">microservices architecture</a>, which is team and service independence.</p><p>This failure mode is illustrated in the sequence diagram below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S1hs8MJwp1ex5DqYWtbBng.png" /><figcaption>Uncoordinated Schema Change Leading to Service Failure</figcaption></figure><h3>Shared resources create shared failures</h3><p>Beyond schema coupling, there is runtime coupling, and I have been in too many war rooms where this was the root cause. When one service experiences a traffic spike (perhaps a popular new product launch), it can saturate the shared database’s connection pool. This quickly escalates beyond a localized problem, often leading to a widespread outage. Suddenly, every other service sharing that database, from Shipping to Payments, also fails because none of them can get a connection. I recall a major incident at a previous role where p95 latency for an unrelated service skyrocketed, tracing directly back to connection-pool saturation, a classic <a href="https://www.educative.io/courses/database-administration-for-beginners?utm_campaign=persona_web_dev_q3_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=webdev_2026&amp;eid=5082902844932096">database administration</a> challenge, caused by another team’s heavy operational load.</p><blockquote><strong>Note:</strong> This is often called the “noisy neighbor” problem. One service’s performance issues directly degrade the performance of all other services sharing the same resource.</blockquote><p>This starkly illustrates the difference between customer truth (the system is down) and a platform constraint (shared resource limits). Customers do not care that the Payments service is healthy; they only know they cannot complete their order because the Orders service exhausted the database connections. This creates a fragile system where a failure in one domain cascades across the entire platform.</p><p>The diagram below visualizes this cascading failure.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_WvaGEKF-VVpx_jBvjrmtw.png" /><figcaption>Database Connection Pool Saturation Leading to Cascading Service Failures</figcaption></figure><h3>Each service must own its data</h3><p>The path forward, which I championed at Databricks and beyond, is the “database-per-service” pattern. Each service has exclusive ownership of its data store and only exposes access via well-defined APIs. For another service to get user data, it must call the UserService API instead of reading the users table directly. This enforces a clear contract and deeply decouples the services. This approach encourages thinking in terms of bounded contexts and <a href="https://martinfowler.com/bliki/DomainDrivenDesign.html">domain-driven design</a>, aligning service ownership metrics with actual team autonomy.</p><blockquote><strong>Practical tip:</strong> The API becomes the official, stable interface. The internal database schema can be changed, refactored, or even completely replaced (e.g., migrating from a <a href="https://www.educative.io/courses/sql-interview-patterns?utm_campaign=persona_web_dev_q3_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=webdev_2026&amp;eid=5082902844932096">relational SQL database</a> to a document store like MongoDB) without impacting any downstream consumers.</blockquote><p>The data-owning service can now evolve its internal schema, optimize its queries, and manage its operational load without fear of breaking its consumers. This leads to more resilient, data-informed product roadmaps where teams can ship independently and predictably. An introduction to backend development often emphasizes this principle of encapsulation as a foundational concept for building scalable systems. This strategic decoupling is the key to achieving true service independence.</p><p>Now, let us discuss how to get there from an existing system.</p><h3>How to untangle a shared database</h3><p>For systems already caught in this anti-pattern, a “big bang” rewrite is a one-way decision fraught with risk. My bias is always toward shipping and learning iteratively. This is where a pragmatic migration path like the Strangler Fig Pattern becomes invaluable. We incrementally pull functionality into a new service with its own database. For a transitional period, data might be written to both the old shared table and the new service’s independent table.</p><blockquote><strong>Practical tip:</strong> Use feature flags to gradually route read traffic to the new service’s API, allowing you to monitor performance and correctness before committing to the switch.</blockquote><p>This dual-write approach, often supported by patterns like sagas or the outbox pattern for transactional integrity, allows consumers to be migrated gradually. We can manage referential joins and consistency using idempotency mechanisms, ensuring intermediate states are consistent long before the physical split. This systematic approach allows us to evolve the architecture without disrupting existing functionality, making conscious trade-offs to keep the lights on while shipping improvements.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c4f326ed5020" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Interview Question That Changed What “build a to-do app” actually tests for in 2026]]></title>
            <link>https://medium.com/@sumit_m/the-interview-question-that-changed-what-build-a-to-do-app-actually-tests-for-039a9d1cf8cc?source=rss-79e4d45fc1a2------2</link>
            <guid isPermaLink="false">https://medium.com/p/039a9d1cf8cc</guid>
            <dc:creator><![CDATA[Sumit M.]]></dc:creator>
            <pubDate>Mon, 28 Sep 2026 10:33:45 GMT</pubDate>
            <atom:updated>2026-09-28T11:26:26.861Z</atom:updated>
            <content:encoded><![CDATA[<p>When I first entered product leadership, I observed a rapid shift in how engineering talent was evaluated. The rise of AI and sophisticated code generators has altered the baseline expectation for candidates, illustrating how coding interviews changed by 2026. A simple “build a todo app” task that merely tests CRUD operations (add, view, edit, delete) is an obsolete exercise for demonstrating engineering maturity beyond CRUD. We are past the point where a take-home test expecting a basic React app with form submission and state management reveals anything meaningful.</p><p>If an AI can generate a functional solution in seconds, the task requires rote execution rather than probing for engineering judgment. This forces hiring managers and technical leaders to look for deeper signals of engineering competence. What companies value in a software interview today are AI-assisted coding interview skills, judgment, debugging, and systems thinking, which extend far beyond a final working output.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l4FXMWl11vlBg9kd33lKsw.png" /><figcaption>AI-Assisted Code Generation and Human Oversight</figcaption></figure><h3>Code review is the new coding test</h3><p>Having scaled critical systems at Meta, I learned that shipping features rarely involve writing code from scratch. It almost always involves understanding, debugging, and improving existing, often complex, codebases. This reality explains why the “todo app” task has evolved, making code review in technical interviews a common format. This is effectively the new debugging test in interviews for 2026. Instead of starting fresh, candidates are often presented with a pre-existing, intentionally flawed todo app (perhaps with subtle concurrency issues or inefficient state updates) and asked to diagnose problems.</p><blockquote><strong>Note:</strong> The goal of this real-world-scenario coding interview is to simulate a situation where perfect, greenfield code is a luxury, not to trick the candidate. The conversation about the bugs is the actual test, highlighting the critical role of debugging during the interview process.</blockquote><p>For instance, asking a candidate to identify why a shared todo list might exhibit eventual consistency issues, or to implement idempotent API calls for critical actions, directly tests their ability to reason about real-world failure modes. This format reveals not only technical skill but also critical thinking and communication. These are indispensable qualities for any engineering team I have built, showcasing an engineer’s true depth more effectively than a simple coding exercise.</p><p>This evolution from writing to reviewing code changes how we evaluate a candidate’s thought process.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EKYcIbI3enqD-nzzNLUrrg.png" /><figcaption>Technical Interview Code Review Simulation</figcaption></figure><h3>Your process reveals your seniority</h3><p>As a VP of Product, I have often seen how a candidate’s development process, rather than the deliverable, correlates with their seniority. At Databricks, when accelerating feature velocity, the clarity of an engineer’s decision-making and their ownership of the “why” was paramount. When evaluating a “todo app,” I look for pragmatic leadership in action; this is how to demonstrate engineering maturity in interviews. I want to see how they manage their <a href="https://cbea.ms/git-commit/">Git commits</a> for clear traceability, how their README explains design choices, and how they anticipate and test for edge cases, especially around non-functional requirements. A mature engineer does not just build; they document trade-offs. This even applies to knowing how to critique your own AI-generated code: explaining its limitations and why one would refactor it for production is a powerful signal.</p><blockquote><strong>Practical tip:</strong> A well-written README that details setup, testing, and crucial architectural trade-offs is one of the strongest positive signals a candidate can send. It shows foresight and respect for the next engineer.</blockquote><p>This detail shows they are not just coding but thinking about the system’s evolving state and operational decay. It demonstrates they are anticipating potential observability needs (metrics/logs/tracing) and considering long-term maintainability, a key skill for any <a href="https://www.educative.io/path/zero-to-hero-in-back-end-web-development?utm_campaign=persona_web_dev_q3_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=webdev_2026&amp;eid=5082902844932096">senior back-end engineer</a>.</p><p>To make this tangible, here is how I mentally map specific signals to seniority levels.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/822/1*imGCXvCRoiklfWklOat1TQ.png" /></figure><h3>The app is a system design prompt</h3><p>In my Meta days, we constantly wrestled with scaling simple concepts into massive, resilient systems. The “todo app” for a senior role becomes a vehicle for a complex discussion, turning into a system-design-for-todo-app interview. The prompt quickly evolves from “build a todo app” to “design a globally distributed, collaborative app with real-time updates, offline support, and conflict resolution.” This is what is evaluated in a collaborative-todo-list interview, and a key skill is knowing how to explain trade-offs in a system design interview. This tests a candidate’s ability to reason about API boundaries, authentication, and data modeling, and how to handle concurrency without data loss.</p><blockquote><strong>Note:</strong> Simply listing technologies (e.g., “I would use Kafka”) without explaining the why, such as for a durable, ordered event log to handle real-time sync, is a significant concern. The reasoning is more important than the tool.</blockquote><p>I expect discussions around <a href="https://stripe.com/blog/idempotency">idempotency for critical write operations</a>, rate limiting to protect backend services, and a clear understanding of eventual vs. strong consistency. A candidate should articulate how they would achieve specific latency SLIs and what observability (metrics/logs/tracing) they would instrument from day one. This involves anticipating failure, planning for scale, and making quantifiable trade-offs, rather than merely focusing on features.</p><p>This thinking naturally scales up to a full system view, which we can visualize.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i5t3my-Q5Ab2BB7Lhz7SPA.png" /><figcaption>Collaborative Global Todo App Architecture</figcaption></figure><h3>How to show your engineering skills</h3><p>To succeed in today’s interviews, candidates must demonstrate their core engineering judgment. This is where communication skills in coding interviews become critical. As a VP of Product, I cannot overstate why communication matters in software interviews; I value engineers who ask clarifying questions, understand customer needs, and articulate <a href="https://martinfowler.com/architecture/#Application%20Architecture:~:text=why%20it%20matters.-,Application%20Architecture,-The%20important%20decisions">platform constraints</a>. They explain why the code works that way, detailing the trade-offs made and how choices impact resilience and evolvability, rather than simply delivering it. These are useful examples of trade-offs candidates should discuss; for instance, detailing the choice between consistency models for different data or how to implement rate limiting shows an understanding of real-world dynamics.</p><blockquote><strong>Practical tip:</strong> Frame your architectural decisions as reversible (“two-way door”) or irreversible (“one-way door”). This demonstrates mature, pragmatic thinking about long-term consequences and the cost of change.</blockquote><p>Highlight your plan for observability, suggesting “instrument metrics for critical user flows to monitor latency SLIs against our SLA” instead of merely “add logging.” Demonstrate ownership of potential failure scenarios, discussing rollbacks and migration strategies, as detailed in Google’s excellent <a href="https://sre.google/sre-book/table-of-contents/">SRE handbook</a>. This approach showcases a holistic, production-minded engineer ready to ship, instrument, and learn at scale, which is the core of our <a href="https://www.educative.io/path/scalability-system-design?utm_campaign=persona_web_dev_q3_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=webdev_2026&amp;eid=5082902844932096">System Design for Engineers</a> learning path.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=039a9d1cf8cc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Build Tooling Fatigue — Why your dev environment might be slower than your production one]]></title>
            <link>https://medium.com/@sumit_m/build-tooling-fatigue-why-your-dev-environment-might-be-slower-than-your-production-one-d829d12cd32a?source=rss-79e4d45fc1a2------2</link>
            <guid isPermaLink="false">https://medium.com/p/d829d12cd32a</guid>
            <dc:creator><![CDATA[Sumit M.]]></dc:creator>
            <pubDate>Mon, 14 Sep 2026 10:45:47 GMT</pubDate>
            <atom:updated>2026-09-14T10:45:47.519Z</atom:updated>
            <content:encoded><![CDATA[<p>Beyond raw build times, the real cost lies in the mental burden of constant decision-making and context-switching. I recall engineers spending half a day debugging a cryptic bundler configuration just to add a new asset type. The advertised performance gain becomes a net loss when the total cost of ownership is factored in. This friction is a significant impediment to productivity, turning promised velocity into a frustrating experience for everyone involved.</p><p><strong>The Paradox of Modern Tooling:</strong> This trade-off is best visualized as a vibrant sports car labeled “Sub-second Hot Reloads” connected by a tangled mess of wires and gears to a frustrated developer. The promise of speed often hides the reality of complex maintenance.</p><p>The result is a development environment that feels fast for trivial changes but becomes unresponsive when faced with non-standard requirements. This hidden complexity is where true velocity is lost.</p><h3>How Layered Tools Create Friction</h3><p>The root of this complexity often lies in how we layer discrete tools together. At Microsoft, I saw firsthand how a seemingly minor configuration change in one layer of our build stack could result in hours of debugging because of undocumented inter-tool dependencies.</p><p>Modern <a href="https://www.educative.io/path/become-a-mern-stack-developer?utm_campaign=persona_web_dev_q1_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=web_dev_2026&amp;eid=5082902844932096">MERN development paths</a> combine specialized frontend build tools, such as a bundler like <a href="https://vitejs.dev/">Vite</a>, a transpiler, a linter, and a formatter. Each of these tools has its own configuration. This layering creates exponential complexity due to configuration fragmentation and conflicts.</p><p>Imagine a project with vite.config.ts, .eslintrc.json, tsconfig.json, and prettier.config.js; a change in one can silently break another. This leads to <a href="https://www.educative.io/path/devops-for-developers?utm_campaign=persona_web_dev_q1_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=web_dev_2026&amp;eid=5082902844932096">CI/CD pipeline complexit</a>y and configuration drift, where the local environment behaves differently from the CI runner, which is a classic source of late-night “it works on my machine” debugging sessions.</p><blockquote><strong>Practical tip:</strong> To combat configuration drift, commit a lockfile for your package manager and use identical <a href="https://www.educative.io/courses/docker-for-developers?utm_campaign=persona_web_dev_q1_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=web_dev_2026&amp;eid=5082902844932096">Docker images</a> for local development and CI environments. This ensures dependency and environment consistency.</blockquote><p>These inter-tool conflicts create recurring friction points, as shown in the following table:</p><h4>Common Tool Conflicts, Impacts, and Mitigations</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hW-IWLWALVAGCMNNp5f_BA.png" /></figure><p>Ultimately, this friction forces engineers to become toolchain experts instead of product builders. The next step is to correctly diagnose the source of this pain.</p><h3>Distinguishing Toil from Tooling</h3><p>During my tenure at Databricks, scaling our platform meant confronting the reality that many “tooling” issues were symptoms of organizational toil. This is a critical distinction: the problem is not always a tool’s inherent flaw, but rather the manual, repetitive work, or “toil,” that surrounds its usage.</p><p>This toil includes tasks like manually updating dependencies across a monorepo, resolving peer dependency conflicts, or writing boilerplate configuration for every new microfrontend. Solutions often get misdirected; teams apply another tool to a process problem.</p><p>This is where a platform engineering approach becomes critical. By leveraging internal developer platforms (IDPs) and targeted automation, you can abstract away this operational overhead. The goal is to make the optimal approach the default, eliminating the manual steps that consume engineering hours and lead to inconsistent, brittle setups across the organization.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*53jc0DorujzMrS6U49LvZQ.png" /></figure><blockquote><strong>Note:</strong> A dedicated platform team is not a luxury reserved for large companies. Even a small team can start by creating shared scripts and standardized project templates to reduce toil.</blockquote><p>This distinction is crucial for applying the correct solution. Picture a Venn diagram with two circles: “Tooling Failures” (e.g., slow compilation, bugs) and “Process Toil” (e.g., manual setup, boilerplate). The overlapping center is exactly where we find Misdirected Solutions. With a clear understanding of the problem type, you can begin a data-driven investigation instead of chasing symptoms blindly.</p><h3>Perform a Pragmatic Toolchain Audit</h3><p>My product leadership instincts highlight this principle: you cannot manage what you do not measure. At Educative, when we consider platform improvements, we start with clear metrics and a thorough understanding of our developers’ pain points. A pragmatic toolchain audit involves quantifying this friction:</p><ul><li><strong>Performance:</strong> Measure local build times, hot-reload latency, and CI pipeline duration.</li><li><strong>Churn:</strong> Instrument how often configuration files are modified or cause CI failures.</li></ul><p>We also conduct team surveys to distinguish what feels bad from what <em>is</em> objectively bad. This data provides a clear baseline, allowing us to frame choices as reversible or one-way. Any subsequent architectural changes must be data-informed, not based on trends. We must treat our internal developer experience with the same rigor we apply to external customer-facing products.</p><blockquote><strong>Watch out:</strong> Be wary of vanity metrics. A 10% faster build time is meaningless if developers spend an extra hour per week debugging opaque configuration issues.</blockquote><p>Here is a simple example of how to begin collecting this performance data by wrapping npm/pnpm scripts with timing utilities in your package.json:</p><pre>{<br>  &quot;name&quot;: &quot;my-project&quot;,<br>  &quot;version&quot;: &quot;1.0.0&quot;,<br>  &quot;description&quot;: &quot;A project demonstrating timed scripts.&quot;,<br>  &quot;scripts&quot;: {<br>    &quot;build&quot;: &quot;pnpm build&quot;,<br>    &quot;build:timed&quot;: &quot;time pnpm build&quot;,<br>    &quot;build:prod&quot;: &quot;time pnpm build --prod&quot;,<br>    &quot;test:timed&quot;: &quot;time pnpm test&quot;,<br>    &quot;lint:timed&quot;: &quot;time pnpm lint&quot;<br>  },<br>  &quot;keywords&quot;: [],<br>  &quot;author&quot;: &quot;&quot;,<br>  &quot;license&quot;: &quot;ISC&quot;<br>}</pre><p>Once you have this baseline, you can make informed decisions about whether to simplify your existing stack or adopt something new.</p><h3>When to Simplify Your Toolchain</h3><p>As a product leader, I constantly evaluate where we are adding accidental complexity. My rule of thumb, hardened by years at Databricks scaling products globally, is that consolidation is often a reversible door and always worth exploring.</p><p>If an audit reveals that multiple tools perform overlapping functions (for example, using ESLint for linting and a separate formatter when a tool like <a href="https://biomejs.dev/">Biome</a> can do both), simplification is the correct path. This means adopting integrated and opinionated solutions that minimize configuration overhead.</p><p>A tangible example is replacing a complex setup of Webpack, Babel, ESLint, and Jest with an integrated solution like Vite with Vitest. This move can drastically reduce configuration files and dependencies, mitigating configuration drift and CI/CD pipeline complexity. It is a direct path to regaining developer focus from tool maintenance to feature delivery.</p><blockquote><strong>Practical tip:</strong> When evaluating an integrated tool, favor those with sensible defaults and clear “eject” paths. This gives you the benefit of simplicity without sacrificing future flexibility.</blockquote><p>The visual impact of this simplification is often dramatic. What once looked like a tangled web of separate tool icons and arrows becomes a streamlined flow with clean, simple connections. Toolchain consolidation reduces complexity and improves maintainability.</p><p>Simplification is a powerful, low-risk strategy; however, sometimes a more significant change is required.</p><h3>Adopt New Tools with Clear Purpose</h3><p>Adopting a new tool, especially a core one, is often a one-way door decision. At Microsoft, when we decided to migrate core systems, it was never for marginal gains.</p><p>Such a migration aimed to solve a critical, quantified bottleneck with a clear ROI that directly impacted our customers. A new tool should only be adopted if it solves a specific, measured problem from your audit with an order-of-magnitude improvement.</p><p>This requires API thinking to ensure seamless integration and a clear decision record outlining the problem, the solution, and the anticipated gains. For example, migrating a large monorepo from Webpack to a <a href="https://www.rust-lang.org/">Rust-based</a> bundler is justified if it cuts CI build times from 20 minutes to 3.</p><p>This directly impacts deployment frequency and developer feedback loops. It is a strategic investment that requires a clear plan for instrumentation and learning post-adoption, not a casual switch.</p><blockquote><strong>Note:</strong> A good decision record for a tool migration includes not just the benefits but also the rollback plan, known risks, and the metrics you will track to validate success.</blockquote><p>Ultimately, building an efficient developer toolchain is an ongoing process of pragmatic auditing, strategic simplification, and data-driven adoption. By focusing on real developer pain points and the total cost of ownership, teams can regain velocity and empower engineers to focus on building products, not just managing complex tools.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d829d12cd32a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Git Blame, But for Prompts: Tracking why an AI made the decision]]></title>
            <link>https://medium.com/@sumit_m/git-blame-but-for-prompts-tracking-why-an-ai-made-the-decision-c58fa341fdc2?source=rss-79e4d45fc1a2------2</link>
            <guid isPermaLink="false">https://medium.com/p/c58fa341fdc2</guid>
            <dc:creator><![CDATA[Sumit M.]]></dc:creator>
            <pubDate>Mon, 14 Sep 2026 10:11:33 GMT</pubDate>
            <atom:updated>2026-09-14T10:11:33.167Z</atom:updated>
            <content:encoded><![CDATA[<p>At Microsoft, we built systems requiring a paper trail for every decision to facilitate auditing. When scaling that to current LLMs, the opaque system problem moves beyond academic curiosity. It presents significant operational and compliance difficulties for anyone tasked with debugging large language models.</p><p>I have seen firsthand how a deviation in an AI’s response can lead to a significant customer issue, particularly in regulated industries or for user-facing applications in WebDev. Pinpointing <em>why</em> an AI pulled incorrect pricing out of a RAG context isn’t just an engineering headache; it’s the core problem of LLM observability. This is comparable to debugging a distributed system without logs, making it impossible to build trustworthy products at scale.</p><p>Understanding how to trace AI decision-making processes is a fundamental requirement for anyone shipping AI into production, especially with frameworks like the <a href="https://artificialintelligenceact.eu/">EU AI Act</a> approaching. This lack of transparency complicates AI regulatory compliance and inevitably leads to losing customer trust and inviting regulatory scrutiny.</p><p>Consider this visualization of the core problem.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QbkqGW4bZ5k9oqhKSOu2mg.png" /><figcaption>Understanding the LLM black box problem</figcaption></figure><p>Understanding AI model outputs through explainability is critical for iteration and trust.</p><h3>Applying feature attribution to text</h3><p>Early in my career, especially at Meta, we encountered challenges interpreting AI outputs from complex models. The concept of ‘feature attribution’ is not new; it is one of the most practical AI explainability techniques and a fundamental aspect of Explainable AI. For LLMs, I view each component of the input prompt (a system instruction, a specific sentence in the RAG context, or the user’s query) as a ‘feature’ driving the output. Exploring feature attribution methods in machine learning, such as <a href="https://shap.readthedocs.io/en/latest/">SHAP</a> and <a href="https://github.com/marcotcr/lime">LIME</a>, offers a pragmatic way to assign ‘influence scores’ to these text snippets, showing their individual contribution. Implementation details are critical for success here.</p><blockquote><strong>Caution:</strong> These methods can be computationally expensive and sometimes unstable, especially with the heavy feature interactions common in prompt engineering. Careful implementation and performance testing are mandatory.</blockquote><p>However, for web applications delivering critical information, obtaining a ’blame score‘ that highlights why a legal AI misread a contract clause by identifying the ambiguous phrase is invaluable. This enables a shift from reactive fixes to proactive improvements, a core principle when implementing feature attribution in AI systems to build explainable AI.</p><p>This illustrates how these features can be conceptually mapped for analysis.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uv2I92W8w35PgUpcb5T4KA.png" /><figcaption>Prompt attribution analysis with SHAP and LIME</figcaption></figure><p>This feature-based view is the first step toward a more rigorous debugging process.</p><h3>Architecting a system for explainability</h3><p>This phase focuses on practical implementation. Building this extends beyond merely an XAI algorithm; it requires a robust architecture to support various AI transparency methods. At Databricks, while developing large-scale data platforms, the principle was ’if it’s not logged, it didn’t happen.‘ For LLM behavior analysis, this translates to a comprehensive logging and versioning strategy. I advocate for a middleware pattern in our WebDev backend services that automatically intercepts every LLM API call.</p><p>We must capture:</p><ul><li>The full prompt and all retrieved RAG context.</li><li>The specific model parameters used (e.g., temperature).</li><li>The raw output and a unique interaction_id.</li></ul><blockquote><strong>Note:</strong> Storing this structured data in an optimized store like <a href="https://www.elastic.co/">Elasticsearch</a> or a time-series database is critical for query performance. Avoid using a generic relational database for this purpose.</blockquote><p>This instrumentation forms the foundation for A/B testing prompt variations, understanding performance bottlenecks, and creating a clear decision record for every AI interaction. This is a key method for ensuring AI accountability. Without such data, product roadmap decisions become speculative.</p><p>A middleware implementation provides a practical starting point for this data capture. If you are brushing up on the fundamentals before writing this type of logic, a quick run through the interactive <a href="https://www.educative.io/courses/learn-python?utm_campaign=persona_web_dev_q1_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=web_dev_2026&amp;eid=5082902844932096">Learn Python 3</a> course is a great way to solidify your backend skills.</p><h4>FastAPI middleware to intercept LLM API calls, log request/response data with a unique ID, and store key points</h4><pre><br>import uuid<br>import json<br>import time<br>from datetime import datetime<br><br>from fastapi import Request, Response<br>from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint<br>from starlette.types import ASGIApp<br><br>class LLMLoggingMiddleware(BaseHTTPMiddleware):<br>    def __init__(self, app: ASGIApp):<br>        super().__init__(app)<br><br>    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -&gt; Response:<br>        request_id = str(uuid.uuid4())  # Generate a unique interaction ID for correlation<br><br>        # Capture request body for logging<br>        request_body_raw = await request.body()<br>        llm_input_data = {}<br>        try:<br>            request_json = json.loads(request_body_raw.decode(&#39;utf-8&#39;))<br>            # Extract key LLM input fields: prompt, context, parameters<br>            llm_input_data = {<br>                &quot;prompt&quot;: request_json.get(&quot;prompt&quot;),<br>                &quot;context&quot;: request_json.get(&quot;context&quot;),<br>                &quot;parameters&quot;: request_json.get(&quot;parameters&quot;),<br>            }<br>        except json.JSONDecodeError:<br>            pass # Handle non-JSON or invalid JSON bodies by logging empty or raw data<br><br>        # Log request details<br>        llm_request_log = {<br>            &quot;id&quot;: request_id,<br>            &quot;timestamp&quot;: datetime.utcnow().isoformat(),<br>            &quot;path&quot;: request.url.path,<br>            &quot;llm_input&quot;: llm_input_data,<br>        }<br>        # Store LLM request log (e.g., `logger.info(llm_request_log)` or database insert)<br><br>        # Re-inject the request body so downstream route handlers can read it<br>        async def receive_body_again():<br>            return {&quot;type&quot;: &quot;http.request&quot;, &quot;body&quot;: request_body_raw}<br>        request._receive = receive_body_again<br><br>        start_time = time.time()<br>        response = await call_next(request) # Process the actual API call<br>        process_time_ms = round((time.time() - start_time) * 1000, 2)<br><br>        # Capture response body for logging<br>        response_body_raw = b&quot;&quot;<br>        async for chunk in response.body_iterator: # Iterate over the response body chunks<br>            response_body_raw += chunk<br><br>        llm_output_data = {}<br>        try:<br>            response_json = json.loads(response_body_raw.decode(&#39;utf-8&#39;))<br>            # Extract key LLM output field: output<br>            llm_output_data = {<br>                &quot;output&quot;: response_json.get(&quot;output&quot;),<br>            }<br>        except json.JSONDecodeError:<br>            pass # Handle non-JSON or invalid JSON responses<br><br>        # Log response details<br>        llm_response_log = {<br>            &quot;id&quot;: request_id,<br>            &quot;duration_ms&quot;: process_time_ms,<br>            &quot;status_code&quot;: response.status_code,<br>            &quot;llm_output&quot;: llm_output_data,<br>        }<br>        # Store LLM response log (e.g., `logger.info(llm_response_log)` or update database entry)<br><br>        # Store full LLM interaction: Combine request and response logs and persist<br>        # Example: `db_client.insert_llm_interaction({&quot;request&quot;: llm_request_log, &quot;response&quot;: llm_response_log})`<br><br>        # Reconstruct and return the original response to the client<br>        return Response(content=response_body_raw, media_type=response.media_type, status_code=response.status_code, headers=dict(response.headers))<br><br></pre><p>With robust logging in place, we can build the analysis layer.</p><h3>Implementing a prompt analysis tool</h3><p>Once the data is available, building the ’prompt blame‘ tool, one of many potential AI accountability tools, is the next step. From my experience shipping products at Microsoft and later at Educative, this is best implemented as a dedicated internal service. It takes an interaction_id, retrieves the full logged context, and systematically applies our chosen feature attribution methods to deconstruct the LLM’s decision. I would architect it as a Python-based microservice using a framework like FastAPI, exposing a well-defined API endpoint.</p><blockquote><strong>Practical tip:</strong> The service should parse the logged prompt into its constituent parts: system instructions, user query, RAG context chunks, before running the attribution. This structured approach simplifies analysis.</blockquote><p>The output would be a JSON object essential for prompt engineering analysis, clearly highlighting influence scores for each component. This shifts ’debugging an LLM‘ from guesswork to a data-informed process. It allows a WebDev team to quickly understand <em>why</em> their support bot misfired and to target fixes, demonstrating how feature attribution gives you a precise surgical tool rather than forcing you into blind prompt tweaking. If you want to dive deeper into full-stack architecture patterns for diagnostic dashboards, check out <a href="https://www.educative.io/catalog/web-development?utm_campaign=persona_web_dev_q1_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=web_dev_2026&amp;eid=5082902844932096">Educative’s Web Development Catalog</a> for practical frameworks. This is a best practice for AI debugging, but keep an eye on computational scalability. Optimizing this service is crucial if you want it to actually survive in production.</p><p>Visualizing this data makes the insights actionable for the team.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bfHklTTT2FfvYAmLrSJ-KA.png" /><figcaption>Prompt analysis tool UI mock-up</figcaption></figure><p>This tool becomes the bridge between a production failure and a targeted solution.</p><h3>Integrating feedback into LLMOps</h3><p>The ROI of a ‘prompt blame’ system, one of the more powerful AI debugging tools, lies not just in isolated fixes but in enabling a continuous feedback loop. At Meta, we emphasized data-driven iteration, and LLM systems operate similarly. Insights from analyzing prompts to interpret AI behavior should directly inform our LLMOps pipeline.</p><p>If the tool consistently flags an outdated policy document in our RAG corpus as the source of incorrect responses for our customer-facing AI, we should not just fix one prompt. We need an automated workflow (a clear decision record) that exemplifies a core technique for AI transparency in decision-making: flagging that document for review, updating it, and retraining our embedding models. This extends beyond technical fixes to achieving product excellence. It ensures our knowledge base remains accurate, our AI guardrails are effective, and we are constantly improving the quality of our AI-powered features. Integrating these findings with monitoring and alerting systems can proactively surface systemic issues before they impact many users, transforming a reactive tool into a proactive quality gate. This pragmatic approach to learning and shipping builds resilient systems.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c58fa341fdc2" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>