<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Saad Mehmood</title>
    <description>The latest articles on Forem by Saad Mehmood (@iamsaadmehmood).</description>
    <link>https://forem.com/iamsaadmehmood</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1131557%2F278b365c-f1ec-4939-81f8-7ab477d2949e.jpeg</url>
      <title>Forem: Saad Mehmood</title>
      <link>https://forem.com/iamsaadmehmood</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/iamsaadmehmood"/>
    <language>en</language>
    <item>
      <title>Next.js API Routes: Patterns That Scale</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Mon, 06 Apr 2026 05:36:29 +0000</pubDate>
      <link>https://forem.com/iamsaadmehmood/nextjs-api-routes-patterns-that-scale-2edj</link>
      <guid>https://forem.com/iamsaadmehmood/nextjs-api-routes-patterns-that-scale-2edj</guid>
      <description>&lt;p&gt;Next.js API routes are quick to add but easy to outgrow. Here are patterns I use to keep them maintainable as the app grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Centralized Error Handling
&lt;/h2&gt;

&lt;p&gt;Don’t repeat try/catch and status codes in every route. Use a small wrapper or middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/api-handler.ts (concept)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withErrorHandling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Internal Server Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it so each route focuses on success logic; errors are handled in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Validate Input
&lt;/h2&gt;

&lt;p&gt;Validate query and body with Zod (or Yup). Return 400 with clear messages instead of blowing up later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;perPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;perPage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same idea for &lt;code&gt;POST&lt;/code&gt;/&lt;code&gt;PUT&lt;/code&gt; body: parse JSON, then validate with Zod.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use the Right HTTP Methods and Status Codes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET&lt;/code&gt; — read; idempotent. Use 200 (with body) or 204 (no body).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST&lt;/code&gt; — create. Return 201 and &lt;code&gt;Location&lt;/code&gt; header when you create a resource.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PUT&lt;/code&gt;/&lt;code&gt;PATCH&lt;/code&gt; — update. 200 with body or 204.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETE&lt;/code&gt; — 204 on success.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Return 400 for bad input, 401 for unauthenticated, 403 for forbidden, 404 when the resource doesn’t exist, 429 when rate-limited.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Cache Where It Makes Sense
&lt;/h2&gt;

&lt;p&gt;For public or semi-static data, use Next.js caching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;revalidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ISR: revalidate every 60 seconds&lt;/span&gt;
&lt;span class="c1"&gt;// or&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;force-static&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For user-specific or private data, avoid caching or use short revalidation and proper auth checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Move Logic Out of the Route File
&lt;/h2&gt;

&lt;p&gt;Keep &lt;code&gt;route.ts&lt;/code&gt; thin: parse request, validate, call a service, return response. Put business logic in &lt;code&gt;lib/&lt;/code&gt; or &lt;code&gt;services/&lt;/code&gt; so you can test and reuse it. Same for DB access: use a repo or client in &lt;code&gt;lib/&lt;/code&gt;, not raw queries in the route.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Auth and Rate Limiting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Check auth (e.g. session or JWT) in middleware or at the start of the handler. Return 401/403 early.&lt;/li&gt;
&lt;li&gt;Add rate limiting (e.g. Upstash Redis or a simple in-memory store) on public or sensitive endpoints so one client can’t overwhelm the API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These patterns keep API routes readable and consistent as you add more endpoints and team members.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>api</category>
      <category>node</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Building Real-Time Features with Firebase (or Supabase) in React Native</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Mon, 06 Apr 2026 05:34:52 +0000</pubDate>
      <link>https://forem.com/iamsaadmehmood/building-real-time-features-with-firebase-or-supabase-in-react-native-1cbg</link>
      <guid>https://forem.com/iamsaadmehmood/building-real-time-features-with-firebase-or-supabase-in-react-native-1cbg</guid>
      <description>&lt;p&gt;Real-time updates—chat, live data, presence—are common in mobile apps. Here’s how I approach them with Firebase or Supabase in React Native.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Firebase or Supabase?
&lt;/h2&gt;

&lt;p&gt;Both give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Realtime listeners&lt;/strong&gt; — Subscribe to a path or query; get updates when data changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth&lt;/strong&gt; — So you can secure data per user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline&lt;/strong&gt; — Firebase has built-in persistence; Supabase can work with local SQLite or similar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use Firebase when the team already uses it or needs other Google services; Supabase when we want Postgres and a more SQL-friendly API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firebase Firestore: Realtime Listeners
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Subscribe to a collection or query&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;firestore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chats&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createdAt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;desc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;}));&lt;/span&gt;
      &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Handle errors, show user-friendly message&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Cleanup on unmount&lt;/span&gt;
&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tips:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;limit()&lt;/code&gt; and paginate; don’t load entire collections.&lt;/li&gt;
&lt;li&gt;Enable offline persistence so the app works without network.&lt;/li&gt;
&lt;li&gt;Secure with Firestore rules: validate &lt;code&gt;request.auth&lt;/code&gt; and restrict reads/writes by user or role.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Supabase: Realtime with Postgres
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres_changes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INSERT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`chat_id=eq.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get realtime from Postgres changes (insert/update/delete). Enable Realtime on the tables you need in the Supabase dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auth First
&lt;/h2&gt;

&lt;p&gt;Both platforms tie data to users. Ensure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User is signed in before subscribing.&lt;/li&gt;
&lt;li&gt;Listeners are scoped to the current user (e.g. &lt;code&gt;userId&lt;/code&gt; in the path or filter).&lt;/li&gt;
&lt;li&gt;On auth state change, unsubscribe from old listeners and subscribe with the new user.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Offline and UX
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Show a “connecting” or “offline” indicator when the client is disconnected.&lt;/li&gt;
&lt;li&gt;Queue critical actions (e.g. send message) and retry when back online.&lt;/li&gt;
&lt;li&gt;For Firebase, use persistence; for Supabase, consider local caching (e.g. React Query + persistence) for a smooth offline experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real-time doesn’t have to mean “complex.” Start with one collection or table, get the listener and cleanup right, then expand from there.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>firebase</category>
      <category>supabase</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Improve React Native App Security: 10 Practices to Evaluate Your Project</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Wed, 11 Mar 2026 15:31:58 +0000</pubDate>
      <link>https://forem.com/iamsaadmehmood/improve-react-native-app-security-10-practices-to-evaluate-your-project-2a89</link>
      <guid>https://forem.com/iamsaadmehmood/improve-react-native-app-security-10-practices-to-evaluate-your-project-2a89</guid>
      <description>&lt;p&gt;After working with React Native for several years, I've put together key security practices you can use to evaluate your project. Apps can't be 100% secure, but you can make hacking difficult and expensive. Here's a checklist that covers the main attack surfaces and what to do about them.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. SSL Pinning
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; API calls can be intercepted with tools like Burp Suite or Charles Proxy, exposing request payloads and responses—including tokens and sensitive data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Implement certificate pinning so the app only trusts your server's certificate (or public key). That way, even if someone installs a custom CA, man-in-the-middle traffic won't be accepted.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;react-native-ssl-pinning&lt;/strong&gt; (or a similar library) to pin your API domain. Pin the certificate or public key hashes and fail closed if they don't match. Remember to update pins before cert rotation so the app doesn't break.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Reverse Engineering
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; APK (Android) and IPA (iOS) can be decompiled using tools like APKTool, jadx, or Hopper, revealing business logic, API shapes, and sometimes secrets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android:&lt;/strong&gt; Enable &lt;strong&gt;ProGuard&lt;/strong&gt; or &lt;strong&gt;R8&lt;/strong&gt; in release builds to obfuscate and shrink code. Remove debug symbols and strip unnecessary metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS:&lt;/strong&gt; Strip symbols in release builds (Xcode does this by default when you build for release). Avoid storing secrets or critical logic in the client when possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;General:&lt;/strong&gt; Move sensitive logic and decisions to the backend. Treat the client as untrusted; validate and authorize on the server.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Secure Local Storage
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; &lt;code&gt;AsyncStorage&lt;/code&gt; (and similar unencrypted storage) can be read on rooted (Android) or jailbroken (iOS) devices, leaking tokens and other sensitive data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Use platform-backed secure storage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;iOS:&lt;/strong&gt; Keychain (encrypted, tied to the device and optionally to biometrics).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android:&lt;/strong&gt; Keystore (hardware-backed when available).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Libraries like &lt;strong&gt;react-native-keychain&lt;/strong&gt; or &lt;strong&gt;react-native-encrypted-storage&lt;/strong&gt; expose a single API for both platforms. Store tokens, refresh tokens, and other secrets there—not in AsyncStorage or plain files.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Screenshot / Screen Recording
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Sensitive screens (OTP, payment forms, private content) can be captured via screenshots or screen recording and leak outside the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android:&lt;/strong&gt; Prevent screenshots on sensitive screens using &lt;code&gt;FLAG_SECURE&lt;/code&gt; (or packages like &lt;strong&gt;react-native-screenshot-prevent&lt;/strong&gt;) so the system doesn't allow screenshots or recording of that window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS:&lt;/strong&gt; Detect screen recording using &lt;code&gt;UIScreen.isCaptured&lt;/code&gt; and hide or blur sensitive views (e.g. OTP field, card number) while recording is active.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;react-native-screen-capture-secure&lt;/strong&gt; can help detect and block screen capture on both platforms. Use it on screens that show PII, payment details, or one-time codes.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Root / Jailbreak Detection
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Rooted (Android) or jailbroken (iOS) devices can bypass app protections, read memory or storage, and tamper with the runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Detect compromised devices using a library like &lt;strong&gt;react-native-jail-monkey&lt;/strong&gt; (or similar). Use the result to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block access entirely for high-risk apps (e.g. banking), or
&lt;/li&gt;
&lt;li&gt;Limit features / show a warning and log the event for analytics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Balance security with UX: some users root for legitimate reasons, so define a clear policy (block vs. warn vs. allow with reduced functionality).&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Code Obfuscation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; The JS bundle and native code can be inspected to extract API keys, endpoints, or business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript:&lt;/strong&gt; Use &lt;strong&gt;javascript-obfuscator&lt;/strong&gt; (or a Metro/bundler plugin that integrates it) to obfuscate the release bundle. Don't rely on obfuscation to protect real secrets—move those to the backend or use short-lived tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native:&lt;/strong&gt; On Android, ProGuard/R8 obfuscate Java/Kotlin. On iOS, strip symbols and avoid embedding secrets in the binary. Obfuscation raises the bar; it doesn't make reverse engineering impossible.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. API Security
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Exposed API keys, long-lived tokens, or unvalidated requests can be abused by attackers who extract them from the app or intercept traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;short-lived tokens&lt;/strong&gt; (e.g. JWT with short expiry) and refresh them via a secure, authenticated flow. Don't put long-lived API keys in the client if they can be moved to the backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate every request on the backend&lt;/strong&gt;—auth, authorization, and input validation. Never trust the client.&lt;/li&gt;
&lt;li&gt;Optionally &lt;strong&gt;sign payloads&lt;/strong&gt; (e.g. HMAC) for critical operations so the server can detect tampering.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Runtime Integrity / Tamper Detection
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; The APK or IPA can be modified (re-signed, patched) to bypass checks, remove root detection, or inject code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Verify app signature (and optionally integrity) at runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android:&lt;/strong&gt; Check that the app is signed with your release keystore (e.g. compare signature to a known value).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS:&lt;/strong&gt; The system enforces code signing; you can add checks for jailbreak and for tampering with key resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the app appears modified or running in an unexpected environment, block or limit functionality and report. This makes casual tampering harder; determined attackers may still find ways around it.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Secure Deep Links
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Malicious apps or links can trigger your deep links with crafted parameters and try to open sensitive screens (e.g. password reset, payment) without proper auth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validate all parameters&lt;/strong&gt;—type, format, and allowed values. Reject or sanitize anything suspicious.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authenticate the user&lt;/strong&gt; before opening sensitive screens. Don't rely on the link alone to grant access; verify session or token on the server if needed.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;verified app links&lt;/strong&gt; (Android App Links / iOS Universal Links) so only your domain can open your app, reducing phishing via custom URL schemes.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  10. CodePush / OTA Updates
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Over-the-air updates (e.g. CodePush, EAS Update) can deliver new JS (and sometimes assets). If the update channel or signing isn't enforced, an attacker could push malicious code to users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only allow signed updates&lt;/strong&gt; from your own backend or the official CodePush/EAS endpoint. Verify the signature or token before applying an update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the source&lt;/strong&gt; of the update (e.g. same app ID, same deployment key) and use HTTPS and certificate pinning for the update endpoint.&lt;/li&gt;
&lt;li&gt;Restrict who can publish updates and audit release history. Treat OTA as a privileged pipeline.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Main risk&lt;/th&gt;
&lt;th&gt;Key mitigation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;SSL&lt;/td&gt;
&lt;td&gt;MITM, traffic interception&lt;/td&gt;
&lt;td&gt;Certificate/public key pinning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Reverse engineering&lt;/td&gt;
&lt;td&gt;Logic and secrets exposed&lt;/td&gt;
&lt;td&gt;ProGuard/R8, strip symbols, move logic to backend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Local storage&lt;/td&gt;
&lt;td&gt;Token/data leak on compromised devices&lt;/td&gt;
&lt;td&gt;Keychain / Keystore (react-native-keychain, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Screenshot/recording&lt;/td&gt;
&lt;td&gt;Sensitive UI captured&lt;/td&gt;
&lt;td&gt;FLAG_SECURE, UIScreen.isCaptured, blur/hide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Root/jailbreak&lt;/td&gt;
&lt;td&gt;Bypass of app protections&lt;/td&gt;
&lt;td&gt;Detect and block or limit (e.g. react-native-jail-monkey)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Code obfuscation&lt;/td&gt;
&lt;td&gt;Keys and logic extracted&lt;/td&gt;
&lt;td&gt;JS obfuscator, ProGuard/R8; no secrets in client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;Abuse of keys and endpoints&lt;/td&gt;
&lt;td&gt;Short-lived tokens, server-side validation, signing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Runtime integrity&lt;/td&gt;
&lt;td&gt;Tampered app bypasses checks&lt;/td&gt;
&lt;td&gt;Signature verification, tamper detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Deep links&lt;/td&gt;
&lt;td&gt;Unauthorized access to screens&lt;/td&gt;
&lt;td&gt;Validate params, authenticate user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;OTA updates&lt;/td&gt;
&lt;td&gt;Malicious update delivery&lt;/td&gt;
&lt;td&gt;Signed updates only, verify source&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use this list to review your React Native app and prioritize the items that matter most for your data and threat model. If you've got practices that have worked well for you, share them in the comments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; | &lt;a href="https://dev.to/iamsaadmehmood"&gt;Dev.to&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>security</category>
      <category>mobile</category>
      <category>ios</category>
    </item>
    <item>
      <title>React Native Performance: What I Measure and Fix First</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Tue, 17 Feb 2026 09:39:10 +0000</pubDate>
      <link>https://forem.com/iamsaadmehmood/react-native-performance-what-i-measure-and-fix-first-3279</link>
      <guid>https://forem.com/iamsaadmehmood/react-native-performance-what-i-measure-and-fix-first-3279</guid>
      <description>&lt;p&gt;When a React Native app feels slow, it’s easy to guess. Here’s what I measure first and the fixes I reach for most often.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Measure
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Startup time&lt;/strong&gt; — Time from tap to first meaningful paint (e.g. home screen visible). I use Flipper, React Native’s built-in perf tools, or a simple timestamp in native code. I compare before/after changes (e.g. new libs, more initial data) so we don’t regress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frame rate (FPS)&lt;/strong&gt; — Especially on scroll and animation. React Native’s “Show Perf Monitor” or Flipper shows JS and UI thread FPS. Drops below 60 (or 120 on capable devices) mean jank. I note which screen or list causes it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;List scroll&lt;/strong&gt; — Long lists are a common bottleneck. I check: Are we using &lt;code&gt;FlatList&lt;/code&gt; (or similar)? Are we rendering too many items or heavy components per row? Are we doing work on the JS thread during scroll?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory&lt;/strong&gt; — I watch for leaks (memory growing over time) and big allocations. Flipper or Xcode/Android Studio profilers show what’s growing. Often it’s images, caches, or listeners not cleaned up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bundle size&lt;/strong&gt; — Large JS bundles slow startup. I check the output of the build (e.g. &lt;code&gt;npx react-native bundle --dev false&lt;/code&gt; and inspect size). I look for heavy dependencies that could be lazy-loaded or replaced.&lt;/p&gt;

&lt;p&gt;I don’t optimize everything—I focus on startup and the screens users hit most (e.g. home, main list). Fix the biggest wins first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Baseline per release&lt;/strong&gt; — Track a small set of numbers per release (e.g. startup time, list FPS on the main feed, memory after 5 minutes). Store them in a doc or CI. When a new feature lands, compare against the baseline so regressions are obvious instead of “it feels slower.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixes I Reach For First
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lists&lt;/strong&gt; — Use &lt;code&gt;FlatList&lt;/code&gt; (or &lt;code&gt;FlashList&lt;/code&gt; if we need more throughput). Implement &lt;code&gt;getItemLayout&lt;/code&gt; when item height is fixed so we skip measurement. Use &lt;code&gt;windowSize&lt;/code&gt; and &lt;code&gt;maxToRenderPerBatch&lt;/code&gt; to limit how many items are in the tree. Memoize list item components so they don’t re-render on every scroll tick. Avoid inline functions and object literals in &lt;code&gt;renderItem&lt;/code&gt;; pass stable props.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Images&lt;/strong&gt; — Use &lt;code&gt;resizeMode&lt;/code&gt; appropriately. Serve correctly sized images from the backend or use a CDN that resizes. Consider &lt;code&gt;react-native-fast-image&lt;/code&gt; for caching if the default cache isn’t enough. Lazy-load off-screen images in lists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Re-renders&lt;/strong&gt; — Find unnecessary re-renders with React DevTools Profiler or “Highlight updates.” Often the fix is: memoize components (&lt;code&gt;React.memo&lt;/code&gt;), stabilize callbacks (&lt;code&gt;useCallback&lt;/code&gt;) and objects/arrays (&lt;code&gt;useMemo&lt;/code&gt;), or lift state so only the part that needs to re-render does. Avoid setting state in render or in effects that run too often.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JS thread&lt;/strong&gt; — Move heavy work off the JS thread: use native modules for heavy computation, or do work in batches (e.g. &lt;code&gt;InteractionManager.runAfterInteractions&lt;/code&gt;). Don’t do large sync operations on the main JS thread during startup or scroll.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Startup&lt;/strong&gt; — Reduce initial JS: lazy-load screens (e.g. React.lazy + code splitting where supported), defer non-critical imports, and trim dependencies. Use Hermes if you’re not already; it often improves startup and memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cleanup&lt;/strong&gt; — Unsubscribe from listeners, clear timers, and cancel requests in &lt;code&gt;useEffect&lt;/code&gt; cleanup. Remove event listeners and close connections so we don’t leak memory or keep unnecessary work running.&lt;/p&gt;

&lt;p&gt;I fix one area at a time, measure again, and repeat. Small, verified improvements add up; random “optimizations” without measurement often don’t.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>performance</category>
      <category>mobile</category>
      <category>productivity</category>
    </item>
    <item>
      <title>From MVP to Production: A React Native Checklist</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Tue, 17 Feb 2026 09:18:09 +0000</pubDate>
      <link>https://forem.com/iamsaadmehmood/from-mvp-to-production-a-react-native-checklist-5982</link>
      <guid>https://forem.com/iamsaadmehmood/from-mvp-to-production-a-react-native-checklist-5982</guid>
      <description>&lt;p&gt;You've built the features. The app works on your machine. Now what?&lt;/p&gt;

&lt;p&gt;The gap between "it works on my machine" and "it's live in the store and we can maintain it" is where a lot of projects get stuck—or ship anyway and pay for it later with broken builds, wrong environments, and "why is staging hitting production?" incidents. After shipping multiple React Native apps to the stores (including large-scale event apps), I've learned that a small set of habits and checks makes that transition predictable instead of painful.&lt;/p&gt;

&lt;p&gt;This post is the checklist I use for every new app and the one I recommend when joining a project that's about to go to production. It's not exhaustive—you'll add items for your stack and compliance needs—but it's the baseline that keeps builds, environments, and releases under control. Use it as a starting point and tighten each area as the product and team grow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Environment &amp;amp; Config
&lt;/h2&gt;

&lt;p&gt;Getting environment and build variants right early avoids the classic mistakes: hardcoded API URLs, staging builds accidentally talking to production, and "it worked in debug but not in release." Spend a little time here and the rest of the pipeline stays sane.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Environment variables&lt;/strong&gt; — Use &lt;code&gt;react-native-config&lt;/code&gt; or Expo's env scheme so API URLs, feature flags, and keys live outside the codebase. Never hardcode them. Have separate configs for dev, staging, and prod so each build talks to the right backend and you can change URLs without a new app release.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Build variants&lt;/strong&gt; — Android: product flavors. iOS: schemes + configurations. Ensure each variant points to the right API and config (see below).&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Versioning&lt;/strong&gt; — Bump &lt;code&gt;versionCode&lt;/code&gt; (Android) and &lt;code&gt;CFBundleShortVersionString&lt;/code&gt; / &lt;code&gt;CFBundleVersion&lt;/code&gt; (iOS) for every release. Store submissions reject duplicate version numbers, and users (and crash reports) need to know which build they're on. Consider automating bumps in CI so you never ship the same version twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Product flavors (Android) and schemes (iOS) — Dev, Staging, Production
&lt;/h3&gt;

&lt;p&gt;Use separate build variants so you can install dev, staging, and production on the same device and keep API/env configs isolated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Android — Product flavors&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;android/app/build.gradle&lt;/code&gt;, define flavors and wire them to env (e.g. &lt;code&gt;react-native-config&lt;/code&gt; or &lt;code&gt;BuildConfig&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;android&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;flavorDimensions&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt;
    &lt;span class="n"&gt;productFlavors&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;dimension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt;
            &lt;span class="n"&gt;applicationIdSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;".dev"&lt;/span&gt;
            &lt;span class="n"&gt;versionNameSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-dev"&lt;/span&gt;
            &lt;span class="n"&gt;resValue&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"app_name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"MyApp Dev"&lt;/span&gt;
            &lt;span class="n"&gt;buildConfigField&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"API_BASE_URL"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"\"https://api-dev.example.com\""&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;staging&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;dimension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt;
            &lt;span class="n"&gt;applicationIdSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;".staging"&lt;/span&gt;
            &lt;span class="n"&gt;versionNameSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-staging"&lt;/span&gt;
            &lt;span class="n"&gt;resValue&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"app_name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"MyApp Staging"&lt;/span&gt;
            &lt;span class="n"&gt;buildConfigField&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"API_BASE_URL"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"\"https://api-staging.example.com\""&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;production&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;dimension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt;
            &lt;span class="n"&gt;resValue&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"app_name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"MyApp"&lt;/span&gt;
            &lt;span class="n"&gt;buildConfigField&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"API_BASE_URL"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"\"https://api.example.com\""&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;dev&lt;/strong&gt; — &lt;code&gt;.dev&lt;/code&gt; suffix so it installs alongside staging/prod. Use dev API and debug-friendly app name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;staging&lt;/strong&gt; — &lt;code&gt;.staging&lt;/code&gt; suffix, staging API. Use for QA and pre-release testing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;production&lt;/strong&gt; — No suffix; this is the store build. Production API only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Build with: &lt;code&gt;npx react-native run-android --variant=devDebug&lt;/code&gt; (or &lt;code&gt;stagingRelease&lt;/code&gt;, &lt;code&gt;productionRelease&lt;/code&gt;, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS — Schemes and configurations&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Configurations&lt;/strong&gt; — In Xcode, duplicate &lt;strong&gt;Debug&lt;/strong&gt; and &lt;strong&gt;Release&lt;/strong&gt; (e.g. &lt;strong&gt;Debug-Dev&lt;/strong&gt;, &lt;strong&gt;Release-Staging&lt;/strong&gt;, &lt;strong&gt;Release-Production&lt;/strong&gt;) or use xcconfig files that set &lt;code&gt;API_BASE_URL&lt;/code&gt; and other env per configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schemes&lt;/strong&gt; — Create schemes &lt;strong&gt;Dev&lt;/strong&gt;, &lt;strong&gt;Staging&lt;/strong&gt;, &lt;strong&gt;Production&lt;/strong&gt;. In each scheme, set the &lt;strong&gt;Run&lt;/strong&gt; and &lt;strong&gt;Archive&lt;/strong&gt; actions to use the right configuration (e.g. Dev → Debug-Dev; Staging → Release-Staging; Production → Release).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle ID suffix&lt;/strong&gt; — In the project’s Build Settings (or per configuration), set &lt;strong&gt;Product Bundle Identifier&lt;/strong&gt; so dev/staging have a suffix (e.g. &lt;code&gt;com.yourapp.dev&lt;/code&gt;, &lt;code&gt;com.yourapp.staging&lt;/code&gt;). That lets you install dev, staging, and production side by side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Display name&lt;/strong&gt; — Set &lt;strong&gt;Display Name&lt;/strong&gt; per configuration (e.g. “MyApp Dev”, “MyApp Staging”) so you can tell them apart on the home screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Run with: &lt;strong&gt;Product → Scheme → Dev&lt;/strong&gt; (or Staging/Production), then &lt;strong&gt;Run&lt;/strong&gt; or &lt;strong&gt;Archive&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you use EAS and &lt;code&gt;app.config.js&lt;/code&gt;, use &lt;strong&gt;environment&lt;/strong&gt; (or &lt;code&gt;APP_ENV&lt;/code&gt;) and different &lt;code&gt;extra&lt;/code&gt; / env in &lt;code&gt;eas.json&lt;/code&gt; profiles (e.g. &lt;code&gt;development&lt;/code&gt;, &lt;code&gt;preview&lt;/code&gt;, &lt;code&gt;production&lt;/code&gt;) so each build gets the correct API URL and app name. You can still use dev-client builds for dev/staging and production builds for the store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing (Minimum Viable)
&lt;/h2&gt;

&lt;p&gt;You don't need 100% coverage on day one—but you do need a safety net for the paths that matter. The goal here is to catch regressions before they reach users and to validate the build you're actually going to submit, not just the debug one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Critical flows&lt;/strong&gt; — At least one E2E or integration test for login/signup and the main user journey. Detox or Maestro can run in CI.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;No obvious regressions&lt;/strong&gt; — Manual test on at least one physical device per platform before release.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Release build&lt;/strong&gt; — Test the exact build you’ll submit (not just debug). ProGuard/minification can hide bugs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  CI/CD
&lt;/h2&gt;

&lt;p&gt;Store builds should come from a repeatable pipeline, not from someone's laptop. That means builds run in the cloud, signing is managed in one place, and submission to TestFlight or Play is a step in the pipeline—not a manual upload after "it built on my machine."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Builds in the cloud&lt;/strong&gt; — Use EAS Build, Bitrise, GitHub Actions, or similar so every build runs in the same environment. No "it works on my machine" for store builds; if the pipeline passes, the build is reproducible and auditable.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Signing&lt;/strong&gt; — Android: keystore stored securely (e.g. in CI secrets or a secrets manager), never in the repo. iOS: certificates and provisioning managed in Apple Developer and wired through EAS or Fastlane so the build step doesn't depend on a single Mac or keychain.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Automated submit&lt;/strong&gt; — After a successful build, automatically submit to TestFlight and Play Internal Testing (e.g. via Fastlane or EAS Submit). Optionally, trigger production submission on a specific tag or branch so releases are traceable and consistent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Store &amp;amp; Compliance
&lt;/h2&gt;

&lt;p&gt;Stores and regulators care about privacy, permissions, and clear communication. Getting this right up front avoids rejections and builds trust with users.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Privacy&lt;/strong&gt; — Provide a privacy policy URL and accurate data collection disclosure in App Store Connect and Play Console. If you use analytics, crash reporting, or third-party SDKs that collect data, say so. Vague or missing disclosures can lead to rejection or legal risk.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Permissions&lt;/strong&gt; — Request only the permissions you actually need, and explain why in permission rationale strings (Android) and Info.plist usage descriptions (iOS). Users are more likely to grant access when they understand the reason; stores may reject apps that ask for broad permissions without justification.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Store assets&lt;/strong&gt; — Screenshots, description, and keywords affect discoverability and conversion. Invest in clear copy and representative screenshots. If you care about install rates, consider A/B testing store assets (where the platform allows) to see what resonates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;p&gt;Once the app is live, you need to know when it breaks and how it behaves. Without crash reporting and some visibility into usage, you're flying blind—and users will tell you about bugs before your metrics do.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Crash reporting&lt;/strong&gt; — Integrate Firebase Crashlytics, Sentry, or similar so every crash generates a report with stack trace and device info. Crashes should create issues (or alerts), not go unnoticed. Triage and fix high-impact crashes before they accumulate bad reviews.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Analytics&lt;/strong&gt; — Track key events that matter for product decisions: signup, purchase, core actions. Don’t over-instrument; focus on events you'll actually use to understand funnels and behavior. Too many events add noise and can complicate privacy compliance.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Performance&lt;/strong&gt; — Monitor startup time and key screens (e.g. time to interactive, list scroll performance). Set a baseline and fix big regressions before they hit production. Slow apps get uninstalled; catching regressions in staging or beta keeps production healthy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;p&gt;Good docs reduce "how do I run this?" and "how do we release?" to a few minutes instead of a day of digging. They also make it possible for someone else to own the release when you're not around.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;README&lt;/strong&gt; — Document how to install dependencies, run the app locally, and build for dev/staging/prod (including which variant or scheme to use). A new team member should be able to get the app running from the README alone, without tribal knowledge.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Runbook&lt;/strong&gt; — Document how to cut a release: which branch or tag to use, how to trigger the CI build, and how to promote from TestFlight/Internal Testing to production. Include who to contact if the build fails or if signing/credentials need to be rotated. Keep it in the repo or in a shared wiki so it's always one link away.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This list is a starting point, not a final rulebook. Add what fits your stack, compliance (e.g. HIPAA, PCI), and team. Use it as a baseline, ship your MVP, then improve each area as you go. If something on your checklist has saved you more than once, share it in the comments or in your own post—tag it so others can find it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>mobile</category>
      <category>checklist</category>
      <category>devops</category>
    </item>
    <item>
      <title>What "Clean Code" Means to Me in a React Codebase</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Thu, 12 Feb 2026 18:38:55 +0000</pubDate>
      <link>https://forem.com/iamsaadmehmood/what-clean-code-means-to-me-in-a-react-codebase-565j</link>
      <guid>https://forem.com/iamsaadmehmood/what-clean-code-means-to-me-in-a-react-codebase-565j</guid>
      <description>&lt;p&gt;“Clean code” can mean anything. In a React (or React Native) codebase, here’s what I actually aim for—concrete, not vague.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. One Job Per Component
&lt;/h2&gt;

&lt;p&gt;A component should do one thing: render a list, show a form, display a card. If it’s doing “fetch data + transform + render + handle three different states,” I split it. Smaller components are easier to read, test, and reuse. I extract hooks for logic (data fetching, form state) so the component stays mostly presentational.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Names That Describe Purpose
&lt;/h2&gt;

&lt;p&gt;Components: &lt;code&gt;UserCard&lt;/code&gt;, &lt;code&gt;OrderSummary&lt;/code&gt;, &lt;code&gt;LoginForm&lt;/code&gt;—names that say what they are. Functions: &lt;code&gt;formatCurrency&lt;/code&gt;, &lt;code&gt;getUserDisplayName&lt;/code&gt;, &lt;code&gt;validateEmail&lt;/code&gt;—names that say what they do. I avoid &lt;code&gt;Wrapper&lt;/code&gt;, &lt;code&gt;Container&lt;/code&gt;, or &lt;code&gt;Component1&lt;/code&gt; unless there’s no better name. Good names reduce the need for comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Fewer Props, Clear Contracts
&lt;/h2&gt;

&lt;p&gt;I keep the prop list short. If a component needs many props, I consider: is it doing too much? Can some props be grouped (e.g. &lt;code&gt;user: { name, avatar }&lt;/code&gt;)? I use TypeScript so the contract is explicit. Optional props are optional for a reason; I don’t add “just in case” props.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. No Magic in the Tree
&lt;/h2&gt;

&lt;p&gt;I avoid inline object/array literals and inline functions in JSX when they’re used as props to children. They break referential equality and cause unnecessary re-renders. I use &lt;code&gt;useMemo&lt;/code&gt; / &lt;code&gt;useCallback&lt;/code&gt; when the value or handler is passed to a memoized child; otherwise I keep them outside the tree or in stable refs. No “it works but I don’t know why” performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. State in the Right Place
&lt;/h2&gt;

&lt;p&gt;I keep state as close as possible to where it’s used. If only one component needs it, it lives there. If two siblings need it, I lift to the parent (or use a small context). I don’t put everything in a global store “for later.” Server state goes in TanStack Query (or similar), not in Redux or Context by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Boundaries for Side Effects
&lt;/h2&gt;

&lt;p&gt;Data fetching, subscriptions, and other side effects live in &lt;code&gt;useEffect&lt;/code&gt; (or in a library like TanStack Query). I don’t fetch in render or mix side effects with pure rendering logic. Cleanup (unsubscribe, abort) goes in the effect’s return. That keeps components predictable and avoids leaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Consistency Over Personal Taste
&lt;/h2&gt;

&lt;p&gt;I follow the project’s existing patterns: file structure, naming, where state lives, how we handle errors. I don’t introduce a new pattern “because I like it” without team agreement. Consistency makes the codebase navigable for everyone.&lt;/p&gt;

&lt;p&gt;Clean code, to me, is: one job per piece, clear names, explicit contracts, minimal magic, state and effects in the right place, and consistency. Boring and repeatable—and that’s the point.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>cleancode</category>
      <category>frontend</category>
      <category>bestofdev</category>
    </item>
    <item>
      <title>Firestore vs Realtime Database: Which Is Best and Why?</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Thu, 05 Feb 2026 06:07:26 +0000</pubDate>
      <link>https://forem.com/iamsaadmehmood/firestore-vs-realtime-database-which-is-best-and-why-1h2c</link>
      <guid>https://forem.com/iamsaadmehmood/firestore-vs-realtime-database-which-is-best-and-why-1h2c</guid>
      <description>&lt;p&gt;Firebase offers two databases: &lt;strong&gt;Realtime Database&lt;/strong&gt; (the original JSON tree) and &lt;strong&gt;Cloud Firestore&lt;/strong&gt; (the newer document database). Both sync in real time and work offline, but they’re built for different needs. Here’s how they compare and which one I pick—and why.fi&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Overview
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Realtime Database&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Firestore&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single JSON tree; data nested under paths&lt;/td&gt;
&lt;td&gt;Collections + documents; each doc can have subcollections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Queries&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deep path reads; no real “query language”&lt;/td&gt;
&lt;td&gt;Rich queries: where, orderBy, limit, compound indexes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single region; scales but with limits on fan-out&lt;/td&gt;
&lt;td&gt;Multi-region; designed to scale with automatic sharding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Offline&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full offline with persistence&lt;/td&gt;
&lt;td&gt;Full offline with persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pricing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Per GB stored + bandwidth; often cheaper at tiny scale&lt;/td&gt;
&lt;td&gt;Per read/write/delete + storage; can get costly with heavy reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very low (single tree, simple reads)&lt;/td&gt;
&lt;td&gt;Slightly higher (more flexible but more work per query)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Realtime Database: What It Is and When It Shines
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is&lt;/strong&gt; — One big JSON tree. You read and write at paths like &lt;code&gt;/users/uid/profile&lt;/code&gt; or &lt;code&gt;/chats/chatId/messages&lt;/code&gt;. Data is nested; you listen to a path and get everything under it (or use shallow reads to limit depth). There’s no “query by field” — you structure the tree so that the path &lt;em&gt;is&lt;/em&gt; your query (e.g. “messages for this chat” = one path).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple&lt;/strong&gt; — No collections, no indexes. You just read/write paths. Great for small apps and prototypes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Very low latency&lt;/strong&gt; — Single tree, direct path access. Good for high-frequency updates (e.g. presence, cursor position, live scores).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cheap at small scale&lt;/strong&gt; — If you have little data and moderate traffic, storage + bandwidth pricing can be lower than Firestore’s read/write model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiny SDK&lt;/strong&gt; — Smaller bundle than Firestore; matters for very constrained environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No real queries&lt;/strong&gt; — You can’t “get all users where role = admin” or “messages where createdAt &amp;gt; X.” You either read a path and filter in the client or denormalize heavily and maintain multiple paths. Complex querying gets messy fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling and depth&lt;/strong&gt; — Deep trees and wide “fan-out” (e.g. one write that touches many paths) don’t scale as well. You hit limits and have to flatten or split data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure&lt;/strong&gt; — Everything is a tree. Relations and many-to-many are awkward; you end up with duplicated data and sync logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When I use it:&lt;/strong&gt; Simple real-time apps (presence, simple chat, live counters), prototypes, or when the data is naturally a tree and query needs are minimal. Also when the team already has a Realtime Database app and migration isn’t worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firestore: What It Is and When It Shines
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is&lt;/strong&gt; — A document database. Data lives in &lt;strong&gt;collections&lt;/strong&gt; (e.g. &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;chats&lt;/code&gt;); each &lt;strong&gt;document&lt;/strong&gt; has an ID and key-value (and nested) data. You can have &lt;strong&gt;subcollections&lt;/strong&gt; (e.g. &lt;code&gt;chats/chatId/messages&lt;/code&gt;). You query with &lt;code&gt;where&lt;/code&gt;, &lt;code&gt;orderBy&lt;/code&gt;, &lt;code&gt;limit&lt;/code&gt;, and compound indexes. Realtime listeners work on collections or query results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real queries&lt;/strong&gt; — “Users where role == 'admin',” “messages where chatId == X orderBy createdAt limit 50.” You model data once and query it many ways. Compound indexes are explicit and predictable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scales better&lt;/strong&gt; — Designed for large apps. Multi-region, automatic scaling. No single-tree bottleneck. Better for many collections and complex access patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clearer data model&lt;/strong&gt; — Documents and subcollections map well to “entities” and relations. Less denormalization than Realtime Database for the same flexibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline&lt;/strong&gt; — Same strong offline support; queries and listeners work offline with persistence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pricing can bite&lt;/strong&gt; — You pay per read/write/delete. Heavy read traffic (e.g. “list all items” on every open) can get expensive if you don’t cache or paginate. You need to think about read volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More setup&lt;/strong&gt; — Indexes, security rules, and data modeling take more thought than “just a path.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slightly higher latency&lt;/strong&gt; — More flexible queries mean a bit more work per request than a single path read. Usually negligible for most apps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When I use it:&lt;/strong&gt; Most new Firebase apps. Anything that needs “list by X,” “filter by Y,” pagination, or multiple views over the same data. Apps that will grow in data and query complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side-by-Side: Key Differences
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Data structure&lt;/strong&gt; — Realtime Database = one tree; Firestore = collections + documents + subcollections. Firestore fits relational-ish and multi-entity models better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Querying&lt;/strong&gt; — Realtime Database = path-based (and maybe client-side filter); Firestore = server-side queries with where/orderBy/limit and indexes. Firestore wins for anything beyond “give me this path.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling&lt;/strong&gt; — Realtime Database scales but has a single-tree and fan-out limits; Firestore is built for scale and multi-region. For growth, Firestore is the safer bet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost&lt;/strong&gt; — At tiny scale and low reads, Realtime Database can be cheaper (GB + bandwidth). At higher read/write volume or complex queries, Firestore’s model is easier to reason about and often comparable or better if you design for it (pagination, cache, avoid unnecessary reads).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offline&lt;/strong&gt; — Both support offline persistence and sync. No clear winner; both are good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Is Best? My Recommendation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For most apps today: Firestore is the better default.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Querying&lt;/strong&gt; — Almost every app eventually needs “get items by condition” or “ordered list with pagination.” Firestore does that natively; Realtime Database forces workarounds and denormalization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling&lt;/strong&gt; — If the app grows, Firestore scales with you. Realtime Database can hit structural limits and require big refactors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data model&lt;/strong&gt; — Documents and collections match how we think about users, chats, posts, etc. Easier to maintain and onboard others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecosystem and future&lt;/strong&gt; — Google is investing in Firestore (multi-region, better tooling). Realtime Database is stable but not where new features land first.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;When Realtime Database is still the best choice:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Very simple real-time only&lt;/strong&gt; — Presence, live counters, or a tiny tree with almost no query needs. Simplicity and latency matter more than flexibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict cost at tiny scale&lt;/strong&gt; — You have almost no reads/writes and want the smallest bill; Realtime Database’s pricing can be lower.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing Realtime Database app&lt;/strong&gt; — Migration has a cost. If the current app is simple and stable, staying on Realtime Database is fine until you need Firestore’s query and scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; Use &lt;strong&gt;Firestore&lt;/strong&gt; for new projects and anything that needs queries or will grow. Use &lt;strong&gt;Realtime Database&lt;/strong&gt; for simple, path-based real-time apps or when you’re already on it and migration isn’t justified. In practice, “which is best” = Firestore for most cases; Realtime Database for a narrow but real set of use cases.&lt;/p&gt;




&lt;p&gt;*Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>mobile</category>
      <category>programming</category>
      <category>firestore</category>
    </item>
    <item>
      <title>Why AI Is Important in Coding Today?</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Thu, 05 Feb 2026 06:00:01 +0000</pubDate>
      <link>https://forem.com/iamsaadmehmood/why-ai-is-important-in-coding-today-2n5l</link>
      <guid>https://forem.com/iamsaadmehmood/why-ai-is-important-in-coding-today-2n5l</guid>
      <description>&lt;p&gt;AI has moved from research labs into the daily workflow of developers. Here’s why it matters for coding today—and how to use it without losing the skills that make you a strong engineer.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Speed Without Sacrificing Quality
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Faster first drafts&lt;/strong&gt; — Boilerplate, config, tests, and repetitive code can be generated in seconds. You spend less time on the predictable parts and more on logic, design, and edge cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consistent patterns&lt;/strong&gt; — AI can follow the patterns you specify (e.g. “use our API handler wrapper,” “follow our component structure”). That keeps style and structure consistent across the codebase and reduces review back-and-forth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick exploration&lt;/strong&gt; — When you need to try a new library, language, or API, AI can outline examples and explain options. You get a head start instead of starting from empty files and docs.&lt;/p&gt;

&lt;p&gt;Speed here means less time on mechanical work, not skipping thinking. You still decide what to build, how it fits the system, and whether the output is correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Learning and Unblocking
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Explain and explore&lt;/strong&gt; — “How does X work?” or “Why did we do it this way?” can be answered in context. AI can summarize docs, compare approaches, and clarify concepts so you learn while you code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unblock faster&lt;/strong&gt; — Stuck on an error, a vague requirement, or an unfamiliar codebase? Describing the problem and pasting relevant code often leads to concrete suggestions. You still validate and adapt the answer, but you spend less time stuck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fill knowledge gaps&lt;/strong&gt; — You don’t have to be an expert in every language, framework, or domain. AI can help with syntax, idioms, and “how do people usually do X?” so you can work outside your comfort zone with more confidence.&lt;/p&gt;

&lt;p&gt;Learning stays with you; AI is a tool to get there faster and to keep growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Code Quality and Consistency
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Catch obvious issues&lt;/strong&gt; — AI can suggest fixes for common bugs, missing null checks, or style issues. It’s another pass over the code, not a replacement for tests and review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refactors and improvements&lt;/strong&gt; — “Make this more readable,” “split this function,” or “add error handling” can be applied across a file or module. You stay in control of the design; AI helps with the edits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation&lt;/strong&gt; — Comments, READMEs, and docstrings can be drafted from the code. That keeps docs closer to what the code actually does and reduces the “I’ll document it later” gap.&lt;/p&gt;

&lt;p&gt;Quality still depends on your judgment: what to change, what to ship, and what “good” means for your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Handling Repetition and Scale
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Repetitive tasks&lt;/strong&gt; — Similar components, API clients, or test cases can be generated from a clear description or example. You tweak and integrate instead of typing the same structure again and again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Large codebases&lt;/strong&gt; — “Where is X used?” or “What calls this function?” can be answered with semantic search and summarization. AI helps you navigate and reason about size and complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintenance&lt;/strong&gt; — Upgrading deps, fixing deprecations, or applying a pattern across many files is tedious. AI can propose changes at scale; you review and apply what’s correct.&lt;/p&gt;

&lt;p&gt;Repetition and scale are where AI can save the most time, as long as you verify the results.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Collaboration and Communication
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Specs and tickets&lt;/strong&gt; — User stories, acceptance criteria, and technical notes can be drafted from a short brief. You refine instead of starting from a blank page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviews and feedback&lt;/strong&gt; — AI can suggest wording for PR comments or docs so feedback is clear and consistent. The decisions remain human; the phrasing can be improved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Onboarding&lt;/strong&gt; — New team members (or your future self) can get summaries of modules, architecture, and “how we do X here.” That speeds up onboarding and keeps tribal knowledge from living only in people’s heads.&lt;/p&gt;

&lt;p&gt;AI supports how teams communicate and collaborate; it doesn’t replace the need for clear thinking and discussion.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Staying Current in a Fast-Moving Ecosystem
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;New tools every week&lt;/strong&gt; — Frameworks, APIs, and best practices change constantly. AI can summarize what’s new, compare “old way vs new way,” and help you adopt upgrades or new libraries without reading every changelog and doc from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lower barrier to try new things&lt;/strong&gt; — Want to try a new language, a new framework, or a new API? AI can outline setup, common patterns, and gotchas so you can experiment quickly instead of spending days in docs before writing a single line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep skills relevant&lt;/strong&gt; — The stack you learned five years ago isn’t the only one that matters. AI helps you bridge gaps so you can contribute to new parts of the codebase or new projects without feeling like you’re starting from zero.&lt;/p&gt;

&lt;p&gt;Staying current is part of the job; AI makes it less overwhelming.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Less Context Switching, More Flow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Answers in the editor&lt;/strong&gt; — You can ask “how do I do X in this library?” or “why is this failing?” without leaving your IDE. No more tab-hopping to docs, Stack Overflow, or internal wikis in the middle of a task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Suggestions in context&lt;/strong&gt; — AI sees the file you’re in, the error you hit, or the code you selected. Suggestions are tailored to your project and your current focus, so you spend less time re-explaining and more time coding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stay in flow&lt;/strong&gt; — Interruptions and context switches cost time and focus. When answers and drafts are a keystroke away, you keep momentum instead of losing it every time you need to look something up.&lt;/p&gt;

&lt;p&gt;Flow matters for quality and speed; AI helps you stay in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Testing, Edge Cases, and “What Could Go Wrong?”
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Generate test cases&lt;/strong&gt; — Describe the behavior you want, and AI can suggest unit tests, integration tests, or edge cases you might have missed. You still decide what to test and what “good enough” is; AI helps you get there faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mock data and fixtures&lt;/strong&gt; — Realistic test data and mocks can be generated from a schema or a short description. Less time building fixtures by hand, more time writing meaningful tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge your assumptions&lt;/strong&gt; — “What could go wrong here?” or “What edge cases should I handle?” can surface failure modes and boundary conditions. You get a second pair of eyes (of a kind) before code goes to production.&lt;/p&gt;

&lt;p&gt;Better coverage and fewer “how did we miss that?” moments—without writing every test from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Security and Best Practices
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Secure-by-default suggestions&lt;/strong&gt; — AI can suggest parameterized queries instead of string concatenation, safe handling of user input, or proper use of auth and secrets. It’s a nudge toward safer patterns when you’re moving fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common vulnerabilities&lt;/strong&gt; — It can flag obvious issues (e.g. hardcoded secrets, unsafe deserialization, or missing validation) and point you to fixes. Not a replacement for security review, but an extra pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best practices in context&lt;/strong&gt; — “How should I structure this?” or “What’s the recommended way to do X in this framework?” keeps you aligned with community and team standards without memorizing every guideline.&lt;/p&gt;

&lt;p&gt;Security and consistency improve when good patterns are suggested where you code, not only in a separate review.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Isn’t Taking Jobs—It’s Changing Them
&lt;/h2&gt;

&lt;p&gt;A common worry is that AI will replace developers. In practice, it’s not taking jobs; it’s changing what the job looks like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI needs a human in the loop&lt;/strong&gt; — It can suggest code, explain concepts, and draft docs, but it can’t own the product. Someone still has to decide what to build, whether the output is correct, how it fits the architecture, and when to ship. AI doesn’t sit in standups, talk to users, or take responsibility for production. Those roles don’t go away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demand for software is growing&lt;/strong&gt; — More products, more integrations, and more automation mean more code and more systems to build and maintain. AI helps each developer do more; it doesn’t remove the need for developers. Teams that use AI well can ship more and take on more work—they don’t typically shrink the team to “one person + AI.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The job shifts, it doesn’t disappear&lt;/strong&gt; — Less time on boilerplate and repetitive tasks means more time on design, debugging, collaboration, and the messy problems that still require human judgment. The skills that matter—understanding requirements, making tradeoffs, reading and refactoring code, and working with others—stay central. AI is a tool that makes those skills more productive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;History repeats&lt;/strong&gt; — Compilers, IDEs, and Google didn’t kill programming; they made it scale. AI is another lever. Developers who learn to use it well become more valuable, not obsolete. The ones at risk are those who refuse to adapt—not because AI “takes” the job, but because others who use AI will outperform them.&lt;/p&gt;

&lt;p&gt;So: AI isn’t taking your job. It’s changing the job. Embrace it as a tool, keep building your judgment and skills, and you stay ahead.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use AI Without Losing Your Edge
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You’re still the architect&lt;/strong&gt; — You decide what to build, how it fits the system, and what “done” looks like. AI suggests and drafts; you decide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify everything&lt;/strong&gt; — Test generated code, check facts, and question suggestions. AI can be wrong or outdated. Your job is to catch that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use it to learn&lt;/strong&gt; — When AI writes something you don’t understand, dig in. Ask for explanations, read the docs, and make sure you could do it yourself next time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep core skills&lt;/strong&gt; — Debugging, design, and reading code are as important as ever. Use AI to go faster, not to avoid learning the fundamentals.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;AI is important in coding today because it makes the mechanical and repetitive parts of the job faster while leaving the creative and judgment-heavy parts to you. Used well, it helps you ship more, learn more, and focus on the work that actually differentiates your product and your skills. The goal isn’t to replace the developer—it’s to make the developer more effective.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>coding</category>
      <category>productivity</category>
      <category>developertools</category>
    </item>
    <item>
      <title># Expo vs Bare React Native</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Fri, 30 Jan 2026 18:20:14 +0000</pubDate>
      <link>https://forem.com/iamsaadmehmood/-expo-vs-bare-react-native-4ek0</link>
      <guid>https://forem.com/iamsaadmehmood/-expo-vs-bare-react-native-4ek0</guid>
      <description>&lt;p&gt;Expo has evolved from “managed only” to a full toolkit that can still eject to bare when needed. Here’s how I think about Expo vs bare React Native in 2025.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expo (EAS, managed or custom dev client)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EAS Build&lt;/strong&gt; — Build in the cloud without Xcode/Android Studio on your machine. Signing and provisioning are handled. Great for CI and for devs who don’t want to maintain native toolchains.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EAS Submit&lt;/strong&gt; — Push builds to TestFlight and Play Internal (or production) from the CLI. Integrates with EAS Build so you can go from commit to store in one pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo SDK&lt;/strong&gt; — Camera, maps, notifications, updates, and more via a consistent API. Often less native code to write and upgrade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over-the-air updates&lt;/strong&gt; — EAS Update lets you ship JS (and asset) changes without a full store release. Useful for hotfixes and A/B tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation and DX&lt;/strong&gt; — Docs are good, and the CLI (e.g. &lt;code&gt;npx expo start&lt;/code&gt;) is smooth. Fast Refresh and dev client make iteration fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom dev client&lt;/strong&gt; — You can add native modules and still use EAS Build and most of the Expo tooling. You’re not locked out of native code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native control&lt;/strong&gt; — For very custom native behavior or bleeding-edge native APIs, you might need to patch or fork. Usually it’s manageable with config plugins or a custom dev client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle and runtime&lt;/strong&gt; — Expo adds some size and abstraction. For most apps it’s fine; for very constrained environments you might care.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning curve&lt;/strong&gt; — If the team has only ever used bare RN, they’ll need to learn EAS and the Expo way of doing things.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When I choose it:&lt;/strong&gt; For most new React Native apps. EAS Build and Submit alone save a lot of pain. I use a custom dev client when I need native modules that aren’t in the default SDK. I use managed when the app stays within Expo’s supported surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expo prebuild: how native projects are generated
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;prebuild&lt;/strong&gt; is the step that creates (or updates) the &lt;code&gt;ios/&lt;/code&gt; and &lt;code&gt;android/&lt;/code&gt; folders from your Expo config. You don’t have to write those projects by hand—Expo generates them from &lt;code&gt;app.json&lt;/code&gt; / &lt;code&gt;app.config.js&lt;/code&gt; and &lt;strong&gt;config plugins&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does&lt;/strong&gt; — Reads your app config and plugin list, then generates a native Xcode and Android project. Config plugins run during prebuild to add permissions, native dependencies, or custom native code (e.g. &lt;code&gt;expo-camera&lt;/code&gt;, &lt;code&gt;expo-notifications&lt;/code&gt;). So you stay in a config-driven workflow even when you need native code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When it runs&lt;/strong&gt; — In &lt;strong&gt;EAS Build&lt;/strong&gt;, prebuild runs by default before each build unless you’ve committed &lt;code&gt;ios/&lt;/code&gt; and &lt;code&gt;android/&lt;/code&gt; and use a profile that skips it. Locally, you run &lt;code&gt;npx expo prebuild&lt;/code&gt; when you want to generate or refresh the native projects. Use &lt;code&gt;npx expo prebuild --clean&lt;/code&gt; to regenerate from scratch (useful after changing plugins or app config).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed vs bare-ish&lt;/strong&gt; — In a &lt;strong&gt;managed&lt;/strong&gt; workflow you often don’t commit &lt;code&gt;ios/&lt;/code&gt; and &lt;code&gt;android/&lt;/code&gt;; EAS Build runs prebuild every time so the native project always matches your config. In a &lt;strong&gt;custom dev client&lt;/strong&gt; or “bare-ish” workflow you might run prebuild once, then commit the native folders and edit them. After that, either keep maintaining those folders or run prebuild again and re-apply your changes (or use config plugins so the changes are reproducible).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it matters&lt;/strong&gt; — prebuild is what lets you add native modules and still use EAS Build and Expo tooling. You’re not stuck choosing between “no native code” and “fully manual native projects.” You get generated native projects that stay in sync with your Expo config and plugins.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bare React Native
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full control&lt;/strong&gt; — Every native file is yours. You can change anything, add any library, and tune the native project exactly how you want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Expo layer&lt;/strong&gt; — Slightly smaller surface area: no Expo runtime or config plugins. Some teams prefer “just RN + native.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing projects&lt;/strong&gt; — Plenty of codebases started bare. If you’re maintaining one, staying bare can be simpler than migrating to Expo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build and signing&lt;/strong&gt; — You own Xcode/Android Studio, certificates, provisioning, and CI. That’s a real cost in time and frustration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Updates&lt;/strong&gt; — You have to upgrade React Native and native deps yourself. Expo abstracts some of that; bare doesn’t.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No EAS Update&lt;/strong&gt; — You can add a different OTA solution (e.g. CodePush), but it’s not built in like EAS Update.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When I choose it:&lt;/strong&gt; When the app needs native behavior Expo doesn’t support (or would require heavy patching), when the team already has a bare setup they’re happy with, or when we need to match an existing bare codebase. I don’t go bare “just because”—I go bare when there’s a clear reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New app, standard needs&lt;/strong&gt; — I start with Expo (managed or custom dev client) and use EAS Build + Submit. I only go bare if we hit a wall.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavy native or existing bare&lt;/strong&gt; — I use or stay with bare when control or existing investment justifies it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2026&lt;/strong&gt; — Expo is production-ready and widely used. The “Expo = can’t use native” idea is outdated. Choose based on your app’s needs and your team’s comfort with native tooling, not on old stereotypes.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expocli</category>
      <category>mobile</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Why I Choose React Native Over Flutter for Cross-Platform Mobile Development</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Wed, 28 Jan 2026 14:49:11 +0000</pubDate>
      <link>https://forem.com/iamsaadmehmood/why-i-choose-react-native-over-flutter-for-cross-platform-mobile-development-2d85</link>
      <guid>https://forem.com/iamsaadmehmood/why-i-choose-react-native-over-flutter-for-cross-platform-mobile-development-2d85</guid>
      <description>&lt;p&gt;React Native and Flutter are both solid choices for building cross-platform mobile apps. Here’s why I reach for React Native more often—and when Flutter might still be the better fit.&lt;/p&gt;




&lt;h2&gt;
  
  
  One Language and Ecosystem for Web and Mobile
&lt;/h2&gt;

&lt;p&gt;React Native lets you use &lt;strong&gt;JavaScript or TypeScript&lt;/strong&gt;—the same languages most web teams already use. If you or your team build React web apps, you’re already most of the way there: same mental model (components, state, props), same tooling (npm/yarn, ESLint, VS Code), and a lot of shared logic. You can reuse utilities, types, and sometimes even UI code between web and mobile.&lt;/p&gt;

&lt;p&gt;Flutter uses &lt;strong&gt;Dart&lt;/strong&gt;, which is a good language but another one to learn and maintain. For teams that are primarily web-focused, React Native reduces context-switching and speeds up onboarding.&lt;/p&gt;




&lt;h2&gt;
  
  
  Huge Ecosystem and Third-Party Libraries
&lt;/h2&gt;

&lt;p&gt;React Native sits on top of the &lt;strong&gt;npm/JavaScript ecosystem&lt;/strong&gt;. There’s a library or solution for almost everything: auth, analytics, payments, maps, charts, camera, and countless native modules. Expo expands that further with a curated set of modules and a smooth dev experience. Finding answers, examples, and Stack Overflow threads is easy.&lt;/p&gt;

&lt;p&gt;Flutter’s pub.dev is growing, but the breadth and depth of the JS ecosystem still give React Native an edge when you need an obscure integration or a quick workaround.&lt;/p&gt;




&lt;h2&gt;
  
  
  Native Components and “Write Once, Run Anywhere” Feel
&lt;/h2&gt;

&lt;p&gt;React Native uses &lt;strong&gt;native UI components&lt;/strong&gt; under the hood. Your views map to real &lt;code&gt;UIView&lt;/code&gt; and &lt;code&gt;View&lt;/code&gt; on iOS and Android, so the app looks and feels like a native app—platform conventions, accessibility, and system controls behave as users expect. You can drop down to native code when you need to.&lt;/p&gt;

&lt;p&gt;Flutter draws everything with its own engine (Skia), which gives pixel-perfect consistency and great performance, but the look can feel more “Flutter-ish” unless you invest in platform-specific theming. React Native’s approach often gets you closer to native look-and-feel with less effort.&lt;/p&gt;




&lt;h2&gt;
  
  
  Faster Iteration and Hot Reload
&lt;/h2&gt;

&lt;p&gt;React Native’s &lt;strong&gt;Fast Refresh&lt;/strong&gt; (and legacy hot reload) lets you see UI and state changes almost instantly without a full rebuild. That tight feedback loop matters a lot for layout tweaks, styling, and debugging. Flutter has hot reload too and it’s good; in practice, both are fast. React Native’s iteration speed is one of the reasons it’s so pleasant for UI-heavy work.&lt;/p&gt;




&lt;h2&gt;
  
  
  One Team for Web and Mobile
&lt;/h2&gt;

&lt;p&gt;If you’re a &lt;strong&gt;full-stack or frontend web developer&lt;/strong&gt;, adding mobile with React Native is a natural step. You don’t need a separate “Flutter/Dart” specialist for the mobile app—the same people who build your React or Next.js front end can own the React Native app. That simplifies hiring, code reviews, and shared standards.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Flutter Might Be the Better Choice
&lt;/h2&gt;

&lt;p&gt;React Native isn’t always the answer. Flutter can be better when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want &lt;strong&gt;very custom, highly branded UI&lt;/strong&gt; and are okay with owning the whole visual layer (e.g. games, design-heavy apps).&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;strong consistency&lt;/strong&gt; across platforms with a single design system and little platform-specific UI.&lt;/li&gt;
&lt;li&gt;Your team already knows &lt;strong&gt;Dart&lt;/strong&gt; or is willing to invest in it.&lt;/li&gt;
&lt;li&gt;You’re building for &lt;strong&gt;desktop (Windows, macOS, Linux)&lt;/strong&gt; or &lt;strong&gt;embedded&lt;/strong&gt; where Flutter’s story is mature.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;I choose &lt;strong&gt;React Native&lt;/strong&gt; when I want to ship cross-platform mobile apps with the same language and ecosystem I use for the web, leverage the npm ecosystem, and keep one mental model for web and mobile. For many product teams and full-stack developers, that makes React Native the more practical default. Flutter is powerful and has real advantages—but for “web devs doing mobile,” React Native often wins.&lt;/p&gt;

&lt;p&gt;What’s your go-to for cross-platform mobile—React Native, Flutter, or something else? Share in the comments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>flutter</category>
      <category>programming</category>
      <category>career</category>
    </item>
    <item>
      <title>Why Understanding Beats Memorizing in Programming</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Tue, 27 Jan 2026 20:30:36 +0000</pubDate>
      <link>https://forem.com/iamsaadmehmood/why-understanding-beats-memorizing-in-programming-4hlg</link>
      <guid>https://forem.com/iamsaadmehmood/why-understanding-beats-memorizing-in-programming-4hlg</guid>
      <description>&lt;p&gt;You don’t need to memorize every syntax or API. You need to understand how things work. Here’s why that shift matters—and how to focus on understanding instead of rote recall.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Memorization Trap
&lt;/h2&gt;

&lt;p&gt;A lot of us were taught to learn by memorizing: syntax, built-in methods, framework APIs, “patterns” from tutorials. It feels productive—we can recite &lt;code&gt;Array.prototype&lt;/code&gt; methods or write a for-loop without looking. But when the problem changes slightly, or we switch languages, or we’re asked “why did you do it that way?” we freeze. Memorization gives the illusion of knowledge. It doesn’t give you the ability to adapt.&lt;/p&gt;

&lt;p&gt;Programming is not a spelling test. The goal isn’t to recall a snippet from memory—it’s to solve a problem by &lt;em&gt;reasoning&lt;/em&gt; about data, flow, and structure. Understanding is what makes that possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Understanding Outweighs Memorizing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. You Can Recover What You Forgot
&lt;/h3&gt;

&lt;p&gt;If you &lt;em&gt;understand&lt;/em&gt; how loops work (repeat this block; condition; iteration), you can reconstruct a &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;, or &lt;code&gt;forEach&lt;/code&gt; in any language. You might look up the exact syntax, but you’re not lost. If you only &lt;em&gt;memorized&lt;/em&gt; one loop form, a different language or style leaves you stuck. Understanding is portable; memorization is brittle.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. You Can Debug and Modify Confidently
&lt;/h3&gt;

&lt;p&gt;Bugs happen when reality doesn’t match what we expect. To fix them, you have to form a mental model (“this variable holds X at this point”), compare it to what the code actually does, and find the mismatch. That’s reasoning—it depends on understanding, not on remembering a solution. Same for changing code: if you understand the flow and the data, you can modify it safely. If you memorized a block, every change feels like guesswork.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. You Can Learn New Things Faster
&lt;/h3&gt;

&lt;p&gt;New frameworks, libraries, and languages keep showing up. No one can memorize them all. But if you understand core ideas—state, side effects, composition, data flow—you recognize them everywhere. New tech becomes “same ideas, different syntax.” Understanding compresses learning; memorization doesn’t scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. You Can Explain and Collaborate
&lt;/h3&gt;

&lt;p&gt;In reviews, pair programming, and interviews, people ask “why?” If you understand your code, you can explain the tradeoffs and the reasoning. If you memorized a pattern, the best you can say is “I saw it in a tutorial.” Understanding makes you a better teammate and a stronger candidate.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. You Retain It Longer
&lt;/h3&gt;

&lt;p&gt;Things you &lt;em&gt;understand&lt;/em&gt; stick. You’ve connected them to principles and to other ideas. Things you only &lt;em&gt;memorized&lt;/em&gt; fade when you don’t use them daily. Investing in understanding pays off over years; cramming for a quick win doesn’t.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. You Don’t Fear Switching Languages
&lt;/h3&gt;

&lt;p&gt;If you’ve only memorized one language or framework, the idea of changing stacks can feel scary—as if you’ll have to start from zero and memorize everything again. When you understand &lt;em&gt;concepts&lt;/em&gt; (loops, functions, data structures, APIs, state), switching languages is mainly new syntax and idioms. The mental model transfers. Memorizers stay locked in; understanders adapt. Reducing that fear is one of the biggest rewards of learning by understanding.&lt;/p&gt;




&lt;h2&gt;
  
  
  What “Understanding” Means in Practice
&lt;/h2&gt;

&lt;p&gt;It doesn’t mean “no documentation.” It means that for the code you write or use, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explain&lt;/strong&gt; what it does and why (in plain language).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trace&lt;/strong&gt; it: given this input, what happens step by step?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relate&lt;/strong&gt; it to a bigger idea (e.g. “this is a reduce because we’re turning a list into one value”).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change&lt;/strong&gt; it: you know what to tweak and what might break.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’re allowed to look up method names and signatures. The goal isn’t to hold everything in your head—it’s to hold the &lt;em&gt;model&lt;/em&gt; of how things work.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Prioritize Understanding
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Learn concepts, then syntax
&lt;/h3&gt;

&lt;p&gt;Before memorizing “how to write a loop in Python,” get clear on &lt;em&gt;what&lt;/em&gt; a loop is (repeat until condition) and &lt;em&gt;when&lt;/em&gt; you’d use it. Concept first; syntax follows and is easy to look up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build a mental model before writing
&lt;/h3&gt;

&lt;p&gt;For each small task, ask: “What data do I have? What do I need? What steps get me there?” Sketch in comments or pseudocode. Then translate to real code. That habit forces understanding instead of copy-paste.&lt;/p&gt;

&lt;h3&gt;
  
  
  Break and fix on purpose
&lt;/h3&gt;

&lt;p&gt;Change one thing—a condition, a variable name, the order of operations—and run the code. See what breaks. Then revert and try another change. You’re not just running code; you’re testing your mental model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explain it to someone (or a rubber duck)
&lt;/h3&gt;

&lt;p&gt;If you can explain a function or a block in simple language, you understand it. If you can’t, you’ve found the gap. Teaching is one of the best ways to deepen understanding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use reference materials without guilt
&lt;/h3&gt;

&lt;p&gt;Docs, Google, and AI are for &lt;em&gt;lookup&lt;/em&gt;—syntax, APIs, examples. Use them. The skill is not “remembering everything,” but “knowing what to look for and how to apply it.” That comes from understanding the concept.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Memorization has a small place: things you use every day (e.g. basic Git commands, your editor shortcuts) become muscle memory. But the core of programming is &lt;em&gt;thinking&lt;/em&gt;: breaking down problems, modeling data and flow, and choosing approaches. That’s driven by understanding.&lt;/p&gt;

&lt;p&gt;So when you learn something new, ask yourself: “Do I understand &lt;em&gt;why&lt;/em&gt; this works, or do I just remember &lt;em&gt;what&lt;/em&gt; to type?” If it’s the latter, pause and close the gap. Your future self—debugging at 2 a.m. or learning a new stack—will thank you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this resonates, drop a comment with how you switch from memorizing to understanding when learning something new. Happy coding.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>learning</category>
      <category>career</category>
    </item>
    <item>
      <title>Why Junior Developers Must Understand the Code—Not Just AI and Vibe Coding</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Tue, 27 Jan 2026 13:31:42 +0000</pubDate>
      <link>https://forem.com/iamsaadmehmood/why-junior-developers-must-understand-the-code-not-just-ai-and-vibe-coding-26f2</link>
      <guid>https://forem.com/iamsaadmehmood/why-junior-developers-must-understand-the-code-not-just-ai-and-vibe-coding-26f2</guid>
      <description>&lt;p&gt;I work with new developers often, and one pattern keeps showing up: heavy reliance on AI tools and copy-pasting from tutorials or the web without really understanding what the code does. This isn’t a rant about AI or “the way things were.” It’s about what happens when you skip the “why” and only do the “what”—and what to do instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem I’m Seeing
&lt;/h2&gt;

&lt;p&gt;More and more juniors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get a task, paste a solution from ChatGPT/Claude/Cursor, and move on.&lt;/li&gt;
&lt;li&gt;Follow a tutorial step-by-step, get something working, but can’t explain the code or change it later.&lt;/li&gt;
&lt;li&gt;Fix bugs by trying the next AI suggestion or Stack Overflow answer until something works, without understanding why.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? Code that “works” until it doesn’t. When it breaks—in prod, in a review, or in an interview—they’re stuck because they never built a mental model of what’s actually happening.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why “It Works” Isn’t Enough
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. You Can’t Debug What You Don’t Understand
&lt;/h3&gt;

&lt;p&gt;If you don’t know what a piece of code is doing, you can’t reason about why it failed. Debugging is basically asking: “Given what this code is supposed to do, where did reality differ?” If you never knew what it was supposed to do, you’re just guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Small Changes Become Scary
&lt;/h3&gt;

&lt;p&gt;Someone asks: “Can we change this so it also does X?” If you pasted the block from an AI or a tutorial and never understood it, every change feels risky. You either avoid it or paste another block and hope it fits. Understanding turns “magic code” into something you can modify confidently.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Interviews and Reviews Expose the Gap
&lt;/h3&gt;

&lt;p&gt;In interviews and code reviews, people ask “why did you do it this way?” or “what does this line do?” If the honest answer is “the AI wrote it” or “the tutorial said so,” that’s a red flag. Knowing the flow, the types, and the tradeoffs is what separates a junior who grows from one who stays stuck.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. You Don’t Build Real Skills
&lt;/h3&gt;

&lt;p&gt;Copy-paste and “AI did it” don’t create lasting skills. The skill is in reading code, tracing execution, and making deliberate decisions. Without that, years of “shipping” can still leave you unable to design or debug on your own.&lt;/p&gt;




&lt;h2&gt;
  
  
  What “Understanding the Code” Actually Means
&lt;/h2&gt;

&lt;p&gt;It doesn’t mean memorizing everything. It means for the code you write or change, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explain&lt;/strong&gt; what it does in plain language (data in → steps → data out).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trace&lt;/strong&gt; it: given this input, what happens line by line (or step by step)?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Justify&lt;/strong&gt; choices: why this function, this structure, this library?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modify&lt;/strong&gt; it safely: where to change things and what might break.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t need to know every API by heart. You need to know how to read the code and the docs and reason about cause and effect.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Use AI and the Web Without Falling Into the Trap
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use AI as a Tutor, Not a Substitute for Thinking
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ask “explain this code line by line” or “what’s the tradeoff between approach A and B?”&lt;/li&gt;
&lt;li&gt;After you get a solution, &lt;strong&gt;step through it&lt;/strong&gt; yourself. Comment each part in your own words. If you can’t, slow down and learn that part before moving on.&lt;/li&gt;
&lt;li&gt;Use AI to generate tests or examples, then &lt;strong&gt;run them and change them&lt;/strong&gt; so you see how behavior changes. That forces understanding.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use Tutorials and Web Answers as Starting Points
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;After copying a snippet, break it: remove a line, change a value. See what breaks and what doesn’t. That teaches you what that part is responsible for.&lt;/li&gt;
&lt;li&gt;Before pasting, try to write a one-sentence summary of what the solution does. If you can’t, treat it as something to learn, not just use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Build a “Understand First, Then Implement” Habit
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;For each feature or bug: spend a few minutes stating the goal and the constraints in your own words.&lt;/li&gt;
&lt;li&gt;Then see if you can sketch the approach (even in pseudocode or comments) before opening AI or Google. You don’t have to get it right—you have to &lt;em&gt;think&lt;/em&gt; first.&lt;/li&gt;
&lt;li&gt;Use AI and the web to fill in details, correct your sketch, or show best practices—not to replace that thinking.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Learning Principles That Help
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fundamentals before frameworks&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Variables, flow, functions, data structures, and basic async. If you understand these, any framework or tool becomes “syntax and patterns” on top of something you already get.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Read and trace before you write&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Open an existing file and trace one user action or one request from top to bottom. Write a short note: “When X happens, the code does A, then B, then C.” Do this often.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Break things on purpose&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Change one thing, run the app, see what happens. Then revert and try another change. This builds a causal model of the codebase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Explain to someone (or a rubber duck)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If you can explain a function or a flow in simple language, you understand it. If you can’t, you’ve found the exact spot to study.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ship small, understandable pieces&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Prefer a simple loop you understand over a “clever” one-liner you don’t. Readable and correct beats short and mysterious.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  A Message to Juniors
&lt;/h2&gt;

&lt;p&gt;Nobody expects you to know everything. Everyone uses AI and the web. The difference is whether you use them to &lt;strong&gt;avoid&lt;/strong&gt; understanding or to &lt;strong&gt;speed up&lt;/strong&gt; understanding.&lt;/p&gt;

&lt;p&gt;When you paste code, pause. Ask yourself: “In 30 minutes, could I explain this to another developer?” If not, block that 30 minutes and make sure you can. That habit is what turns you from someone who depends on AI and webcoding into someone who can debug, redesign, and grow—with or without tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note to Seniors and Teams
&lt;/h2&gt;

&lt;p&gt;If you’re mentoring or hiring: assume good intent. Juniors aren’t “cheating” by using AI; they’re trying to be productive. The fix isn’t to ban tools—it’s to set the bar clearly: “We value code you can explain and modify.” Do code reviews that ask “what does this do?” and “why this approach?” Pair on debugging and walk through the execution flow together. Model “I don’t know—let me read the code and find out.” That’s how you help new developers build the habit of understanding, not just delivering.&lt;/p&gt;




&lt;p&gt;If this resonates—especially if you’ve been on either side of this—drop a comment or share how you balance using AI with actually understanding the code. Happy coding, and here’s to writing code we can explain.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
      <category>career</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
