<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ optimization - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ optimization - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 12 Jul 2026 08:54:53 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/optimization/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ The EKS Cost Optimization Handbook: Reduce Your AWS Bill by 60% Using Karpenter and Rightsizing ]]>
                </title>
                <description>
                    <![CDATA[ This handbook is a complete guide to the 7-step playbook that took one EKS bill from \(85,000/month to \)34,000/month — without touching a single line of product code. I've audited EKS clusters at mor ]]>
                </description>
                <link>https://www.freecodecamp.org/news/eks-cost-optimization-reduce-your-aws-bill-using-karpenter-and-rightsizing/</link>
                <guid isPermaLink="false">6a396515fa8e37864960ddb6</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayobami Adejumo ]]>
                </dc:creator>
                <pubDate>Mon, 22 Jun 2026 16:38:45 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/cd12d552-bcf2-466a-a98e-7674c436afaa.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>This handbook is a complete guide to the 7-step playbook that took one EKS bill from \(85,000/month to \)34,000/month — without touching a single line of product code.</p>
<p>I've audited EKS clusters at more than 10 companies. The same waste patterns appear every time: over-provisioned nodes, cross-AZ data transfer, idle EBS volumes, and so on. And the most expensive mistake of all: buying compute commitments before rightsizing.</p>
<p>This handbook is the fix. I've used this 7-step playbook to reduce EKS costs by 50–60% at every company where I've implemented it. There are no product code changes, and no downtime. Just infrastructure optimization executed in the right order.</p>
<p>By the end of this guide, you'll know how to right-size pod resource requests, implement Karpenter for intelligent bin-packing and Spot diversification, migrate compatible workloads to Graviton for 20% cheaper compute, and eliminate NAT Gateway charges entirely with VPC endpoints.</p>
<p>All Terraform modules, NodePool templates, and automation scripts referenced in this guide are available in the companion repository at <a href="https://github.com/aayostem/eks-cost-optimization">github.com/aayostem/eks-cost-optimization</a>. The repo includes ready-to-deploy configurations for every step so you can move from reading to implementing in the same afternoon.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-youll-learn">What You'll Learn</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-part-1-the-baseline-where-your-eks-money-is-going">Part 1: The Baseline — Where Your EKS Money Is Going</a></p>
</li>
<li><p><a href="#heading-part-2-right-sizing-pod-resource-requests">Part 2: Right-Sizing Pod Resource Requests</a></p>
</li>
<li><p><a href="#heading-part-3-karpenter-for-bin-packing-and-spot-diversification">Part 3: Karpenter for Bin-Packing and Spot Diversification</a></p>
</li>
<li><p><a href="#heading-part-4-graviton-migration">Part 4: Graviton Migration</a></p>
</li>
<li><p><a href="#heading-part-5-vpc-endpoints-for-data-transfer">Part 5: VPC Endpoints for Data Transfer</a></p>
</li>
<li><p><a href="#heading-part-6-ebs-volume-optimisation">Part 6: EBS Volume Optimisation</a></p>
</li>
<li><p><a href="#heading-part-7-load-balancer-consolidation">Part 7: Load Balancer Consolidation</a></p>
</li>
<li><p><a href="#heading-the-complete-7-step-sequence">The Complete 7-Step Sequence</a></p>
</li>
<li><p><a href="#heading-best-practices-for-eks-cost-optimisation">Best Practices Summary</a></p>
</li>
<li><p><a href="#heading-resources">Resources</a></p>
</li>
</ul>
<h2 id="heading-what-youll-learn">What You'll Learn</h2>
<ul>
<li><p>How to right-size pod resource requests using VPA recommendations</p>
</li>
<li><p>The complete Karpenter setup with Spot diversification and automatic consolidation</p>
</li>
<li><p>Graviton3 migration for all non-GPU workloads</p>
</li>
<li><p>VPC endpoints to eliminate NAT Gateway data transfer charges</p>
</li>
<li><p>EBS gp2 to gp3 migration — 20% cheaper with zero performance loss</p>
</li>
<li><p>Load balancer consolidation with shared Ingress</p>
</li>
<li><p>The 7-step sequence that maximises ROI — and why the order isn't optional</p>
</li>
</ul>
<p>Let's dive in.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before following along, you should have:</p>
<p><strong>Knowledge:</strong></p>
<ul>
<li><p>Working familiarity with Kubernetes — you can deploy an application and inspect pods</p>
</li>
<li><p>Basic AWS knowledge — you understand EC2 instance types, VPCs, and EBS volumes</p>
</li>
<li><p>Comfort reading Terraform HCL and Kubernetes YAML</p>
</li>
</ul>
<p><strong>Tools and access:</strong></p>
<ul>
<li><p>An existing EKS cluster running Kubernetes 1.27 or later</p>
</li>
<li><p><code>kubectl</code> configured and pointing at your cluster</p>
</li>
<li><p>AWS CLI v2 installed and authenticated with appropriate permissions</p>
</li>
<li><p>Helm 3 installed (for Karpenter and Kubecost)</p>
</li>
<li><p><a href="https://github.com/kubernetes-sigs/metrics-server">Metrics Server</a> installed in your cluster</p>
</li>
</ul>
<p><strong>Companion repository:</strong> Clone the repo before starting. It contains all YAML, Terraform, and shell scripts referenced in this guide:</p>
<pre><code class="language-bash">git clone https://github.com/aayostem/eks-cost-optimization
cd eks-cost-optimization
</code></pre>
<p><strong>Estimated savings:</strong> For a cluster running at \(85,000/month with typical over-provisioning, expect \)40,000–55,000/month in savings after completing all 7 steps. Smaller clusters under $10,000/month typically see 40–50% reduction.</p>
<h2 id="heading-part-1-the-baseline-where-your-eks-money-is-going">Part 1: The Baseline — Where Your EKS Money Is Going</h2>
<h3 id="heading-11-the-typical-eks-cost-breakdown">1.1 The Typical EKS Cost Breakdown</h3>
<p>Before touching anything, you need to know exactly where the money is going. Optimising the wrong category first is how teams waste weeks of engineering time and see no meaningful reduction.</p>
<p>Here's what a typical $85,000/month EKS cluster looks like when you break it down:</p>
<table>
<thead>
<tr>
<th>Category</th>
<th>Monthly Cost</th>
<th>Percentage</th>
<th>Waste Potential</th>
</tr>
</thead>
<tbody><tr>
<td>Compute (EC2 nodes)</td>
<td>$52,000</td>
<td>61%</td>
<td>High — over-provisioning, wrong instance types</td>
</tr>
<tr>
<td>Data Transfer</td>
<td>$15,300</td>
<td>18%</td>
<td>Very High — cross-AZ and NAT Gateway charges</td>
</tr>
<tr>
<td>Storage (EBS volumes)</td>
<td>$10,200</td>
<td>12%</td>
<td>Medium — unattached volumes and gp2 vs gp3</td>
</tr>
<tr>
<td>Load Balancers</td>
<td>$4,250</td>
<td>5%</td>
<td>Low to Medium — single-service ALBs</td>
</tr>
<tr>
<td>EKS Control Plane</td>
<td>$72</td>
<td>&lt;1%</td>
<td>None — this is a fixed cost</td>
</tr>
<tr>
<td>Other</td>
<td>$3,178</td>
<td>4%</td>
<td>Low</td>
</tr>
</tbody></table>
<p>Compute and Data Transfer together represent 79% of the bill and account for 90% of the correctable waste. Those are the targets.</p>
<p>Run this command to see your own breakdown before starting anything:</p>
<pre><code class="language-bash"># Pull last month's cost breakdown by service
# Save this output — it becomes your before number
aws ce get-cost-and-usage \
  --time-period Start=\((date -d 'last month' +%Y-%m-01),End=\)(date +%Y-%m-01) \
  --granularity MONTHLY \
  --group-by Type=DIMENSION,Key=SERVICE \
  --metrics UnblendedCost \
  --query 'ResultsByTime[0].Groups[*].{Service:Keys[0],Cost:Metrics.UnblendedCost.Amount}' \
  --output table | sort -k3 -rn
</code></pre>
<p>Screenshot the output and save it. You'll compare against it after each step to verify actual savings before moving to the next one.</p>
<h3 id="heading-12-the-most-expensive-mistake-wrong-optimisation-order">1.2 The Most Expensive Mistake: Wrong Optimisation Order</h3>
<p>Here's what most teams do when they get a large AWS bill:</p>
<ol>
<li><p>Buy Savings Plans immediately, locking in waste at a 30% discount</p>
</li>
<li><p>Then implement Karpenter, discovering they've over-committed the wrong instance family</p>
</li>
<li><p>Then migrate to Graviton, discovering their Savings Plan doesn't cover ARM instances</p>
</li>
</ol>
<p>The result: a 12–36 month commitment paying for waste they could have eliminated in three weeks.</p>
<p>The correct sequence is:</p>
<pre><code class="language-plaintext">Step 1: Right-size pod requests        ← Always first
Step 2: Implement Karpenter            ← Dynamic provisioning on rightsized requests
Step 3: Enable Spot for non-prod       ← Karpenter handles fallback automatically
Step 4: Migrate to Graviton            ← Karpenter makes this seamless
Step 5: Add VPC endpoints              ← Eliminate data transfer charges
Step 6: Optimise EBS volumes           ← Quick win, run alongside other steps
Step 7: Consolidate load balancers     ← Final structural cleanup
</code></pre>
<p>Then, and only then, buy Savings Plans — against the optimised baseline you've just established.</p>
<p>The one rule: optimise first, then commit. Every step before the Savings Plan purchase reduces what you're locking in for 1–3 years.</p>
<h2 id="heading-part-2-right-sizing-pod-resource-requests">Part 2: Right-Sizing Pod Resource Requests</h2>
<h3 id="heading-21-why-over-provisioned-requests-are-so-expensive">2.1 Why Over-Provisioned Requests Are So Expensive</h3>
<p>Kubernetes schedules pods based on resource <em>requests</em> — not actual usage. A pod that requests 2 vCPUs and 4GB of memory requires a node with that capacity available, regardless of whether the pod is actually using it.</p>
<p>Here's the incorrect approach with the requests set to worst-case peak estimates:</p>
<pre><code class="language-yaml"># Bad: Resource requests set during initial deployment, never revisited
# This pod actually uses 250m CPU and 512Mi memory on average
resources:
  requests:
    cpu: "2"        # 8x more than actual usage
    memory: "4Gi"   # 8x more than actual usage
  limits:
    cpu: "4"
    memory: "8Gi"
</code></pre>
<p>When every pod is over-requested by 8x, your cluster needs 8x more nodes than your workloads actually require. That's where the 61% compute line in your bill comes from.</p>
<p>First, verify actual usage before changing anything:</p>
<pre><code class="language-bash"># Install Metrics Server if not already running
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# Check actual CPU and memory usage per pod
# Compare these numbers against your current resource requests
kubectl top pods --all-namespaces --sort-by=cpu
</code></pre>
<p>Expected output showing the typical gap:</p>
<pre><code class="language-plaintext">NAMESPACE     NAME                    CPU(cores)   MEMORY(bytes)
production    payment-api-xxx         25m          128Mi
production    user-api-xxx            15m          96Mi
production    notification-svc-xxx    5m           64Mi
staging       worker-xxx              10m          256Mi
</code></pre>
<p>If your pods are requesting 2 CPU cores each but using 25m–15m cores in practice, you have a 50–80x over-request ratio. Every node in your cluster is mostly empty space you're paying for.</p>
<h3 id="heading-22-using-the-vertical-pod-autoscaler-for-recommendations">2.2 Using the Vertical Pod Autoscaler for Recommendations</h3>
<p>The Vertical Pod Autoscaler (VPA) is a Kubernetes component that analyses historical CPU and memory usage for each deployment and recommends optimal resource requests. You use it in recommendation-only mode first — it tells you what to set without changing anything automatically, so you can review and apply the changes yourself with full control.</p>
<p>Here's the correct implementation:</p>
<pre><code class="language-yaml"># Good: VPA in recommendation-only mode
# Watches your pod's actual usage for 24+ hours, then recommends right-sized requests
# updateMode: "Off" means it only recommends — it never restarts your pods
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: payment-api-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: payment-api
  updatePolicy:
    updateMode: "Off"   # Recommendation only — you apply manually after review
  resourcePolicy:
    containerPolicies:
    - containerName: "*"
      minAllowed:
        cpu: "100m"     # VPA will never recommend below this floor
        memory: "256Mi"
      maxAllowed:
        cpu: "2"        # VPA will never recommend above this ceiling
        memory: "4Gi"
</code></pre>
<p>Install VPA and retrieve recommendations:</p>
<pre><code class="language-bash"># Install VPA components
kubectl apply -f https://github.com/kubernetes/autoscaler/releases/download/vertical-pod-autoscaler-1.0.0/vpa-v1.0.0.yaml

# Apply the VPA manifest for each deployment you want to right-size
kubectl apply -f vpa/payment-api-vpa.yaml

# Wait 24 hours for VPA to collect usage data, then check recommendations
kubectl describe vpa payment-api-vpa -n production
</code></pre>
<p>What a VPA recommendation looks like:</p>
<pre><code class="language-plaintext">Recommendation:
  Container Recommendations:
    Container Name: payment-api
    Lower Bound:
      cpu:     50m
      memory:  128Mi
    Target:
      cpu:     250m      ← Set your requests to this value
      memory:  512Mi     ← Set your requests to this value
    Upper Bound:
      cpu:     500m
      memory:  1Gi
</code></pre>
<p>Apply the recommendation to your deployment:</p>
<pre><code class="language-yaml"># Good: Right-sized requests based on VPA Target recommendation
resources:
  requests:
    cpu: "250m"     # Down from 2000m — an 8x reduction
    memory: "512Mi" # Down from 4096Mi — an 8x reduction
  limits:
    cpu: "500m"     # 2x the request — headroom for genuine spikes
    memory: "1Gi"   # 2x the request
</code></pre>
<p>All VPA manifests for common deployment types are in <code>vpa/</code> in the <a href="https://github.com/aayostem/eks-cost-optimization/tree/main/vpa">companion repo</a>.</p>
<h3 id="heading-23-the-roi-of-right-sizing">2.3 The ROI of Right-Sizing</h3>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Before</th>
<th>After</th>
<th>Improvement</th>
</tr>
</thead>
<tbody><tr>
<td>Average CPU utilisation</td>
<td>18%</td>
<td>65%</td>
<td>+47 percentage points</td>
</tr>
<tr>
<td>Node count required</td>
<td>42</td>
<td>28</td>
<td>-33%</td>
</tr>
<tr>
<td>Monthly compute cost</td>
<td>$52,000</td>
<td>$36,400</td>
<td>-$15,600/month</td>
</tr>
</tbody></table>
<p>Verify the improvement after applying recommendations:</p>
<pre><code class="language-bash"># Check cluster-level utilisation after right-sizing
# Target: 60–75% CPU and memory utilisation across nodes
kubectl top nodes
</code></pre>
<h2 id="heading-part-3-karpenter-for-bin-packing-and-spot-diversification">Part 3: Karpenter for Bin-Packing and Spot Diversification</h2>
<p>Karpenter is an open-source Kubernetes node provisioner built by AWS and donated to the CNCF.</p>
<p>Where the default Kubernetes Cluster Autoscaler scales pre-configured node groups up and down, Karpenter watches the actual resource requests of pending pods and provisions exactly the right EC2 instance type to satisfy them — selecting dynamically from thousands of available instance families rather than the two or three you pre-configured. It also continuously monitors running nodes for underutilisation and consolidates workloads onto fewer nodes, terminating the empty ones automatically.</p>
<p>The result is a cluster that is always sized to what your workloads actually need right now, not what you anticipated at setup time.</p>
<h3 id="heading-31-the-ceiling-with-cluster-autoscaler">3.1 The Ceiling with Cluster Autoscaler</h3>
<p>Cluster Autoscaler works with pre-defined node groups. You configure which instance types are available and it scales those groups up and down.</p>
<p>The limitation is that it can only provision instances from the types you pre-configured. It can't dynamically select the right instance type based on what the workload actually needs right now.</p>
<p>Here's the incorrect approach using static node groups:</p>
<pre><code class="language-bash"># Bad: Two static node groups, each over-provisioning against worst-case scenarios
# CPU-optimised group runs even when workloads are memory-bound
# Memory-optimised group runs even when workloads are CPU-bound
eksctl create nodegroup \
  --cluster my-cluster \
  --name cpu-optimized \
  --instance-types c5.2xlarge \
  --nodes-min 5 --nodes-max 20

eksctl create nodegroup \
  --cluster my-cluster \
  --name memory-optimized \
  --instance-types r5.2xlarge \
  --nodes-min 3 --nodes-max 10
</code></pre>
<p>You're provisioning for the worst case in each family simultaneously. At any given moment, one group is underutilised while the other is scaling. Neither is right.</p>
<h3 id="heading-32-how-karpenter-solves-this">3.2 How Karpenter Solves This</h3>
<p>Karpenter watches the actual resource requests of pending pods and provisions exactly the right instance type to fit them. It selects from thousands of available instance types, not just the two you pre-configured. It also consolidates running workloads onto fewer nodes when utilisation drops, automatically terminating underutilised nodes.</p>
<p>Here's the correct implementation:</p>
<pre><code class="language-yaml"># Good: Karpenter NodePool
# Karpenter selects the optimal instance type based on pending pod requirements
# Tries Spot first, falls back to On-Demand automatically when Spot isn't available
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      requirements:
        # Allow both x86 and ARM (Graviton) — Karpenter picks the cheaper option
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
        # Try Spot first, fall back to On-Demand if unavailable
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        # Exclude families with poor price-to-performance ratio
        - key: karpenter.k8s.aws/instance-family
          operator: NotIn
          values: ["t2", "t3a"]
  limits:
    cpu: "1000"
    memory: "4000Gi"
  disruption:
    # Remove underutilised nodes and reschedule their pods automatically
    consolidationPolicy: WhenUnderutilized
    # Recycle nodes after 30 days to ensure fresh, patched AMIs
    expireAfter: 720h
</code></pre>
<p>What each setting does:</p>
<ul>
<li><p><code>consolidationPolicy: WhenUnderutilized</code>: Karpenter continuously monitors node utilisation and removes underused nodes, moving their pods elsewhere. Your node count decreases automatically as load drops without any manual intervention.</p>
</li>
<li><p><code>expireAfter: 720h</code>: Nodes older than 30 days are gracefully replaced, ensuring your infrastructure always runs the latest EKS-optimised AMI with current security patches.</p>
</li>
<li><p><code>values: ["spot", "on-demand"]</code>: Karpenter attempts Spot capacity first. If Spot is unavailable for the requested instance type, it falls back to On-Demand with no alerts and no manual action required.</p>
</li>
</ul>
<p>Migrating from Cluster Autoscaler safely:</p>
<pre><code class="language-bash"># Step 1: Install Karpenter alongside Cluster Autoscaler — do not remove CAS yet
helm repo add karpenter https://charts.karpenter.sh
helm install karpenter karpenter/karpenter \
  --namespace karpenter \
  --create-namespace \
  --set settings.clusterName=your-cluster-name

# Step 2: Apply NodePool and NodeClass configuration
kubectl apply -f karpenter/nodepool.yaml
kubectl apply -f karpenter/nodeclass.yaml

# Step 3: Taint existing legacy nodes so new pods schedule on Karpenter nodes
# This migrates workloads gradually — zero downtime
kubectl taint nodes -l eks.amazonaws.com/nodegroup=cpu-optimized \
  group=legacy:NoSchedule

# Step 4: Watch pods reschedule to Karpenter-managed nodes over the next hour
kubectl get pods -o wide --all-namespaces | grep -v legacy

# Step 5: After 30 days of stable operation, remove the old node groups
eksctl delete nodegroup --cluster my-cluster --name cpu-optimized
eksctl delete nodegroup --cluster my-cluster --name memory-optimized
</code></pre>
<p>Ready-to-deploy NodePool and NodeClass templates are in <code>karpenter/</code> in the <a href="https://github.com/aayostem/eks-cost-optimization/tree/main/karpenter">companion repo</a>.</p>
<h3 id="heading-33-spot-instances-for-non-production-workloads">3.3 Spot Instances for Non-Production Workloads</h3>
<p>Staging and development workloads don't need the reliability guarantees of On-Demand instances. Moving them to Spot saves 60–90% on those node costs. Karpenter handles Spot interruptions by rescheduling pods automatically. For stateless workloads, interruptions are invisible to users.</p>
<pre><code class="language-yaml"># Good: Spot-only NodePool for staging environments
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: staging-spot
spec:
  template:
    metadata:
      labels:
        billing/environment: staging
    spec:
      taints:
        - key: environment
          value: staging
          effect: NoSchedule  # Only pods that tolerate this taint schedule here
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"]   # Spot only for non-production
  disruption:
    consolidationPolicy: WhenUnderutilized
</code></pre>
<h3 id="heading-34-the-roi-of-karpenter-and-spot">3.4 The ROI of Karpenter and Spot</h3>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Before (Cluster Autoscaler)</th>
<th>After (Karpenter + Spot)</th>
<th>Improvement</th>
</tr>
</thead>
<tbody><tr>
<td>Average node count</td>
<td>28</td>
<td>18</td>
<td>-36%</td>
</tr>
<tr>
<td>Average CPU utilisation</td>
<td>65%</td>
<td>82%</td>
<td>+17 percentage points</td>
</tr>
<tr>
<td>Staging environment cost</td>
<td>$8,000/month</td>
<td>$2,400/month</td>
<td>-70%</td>
</tr>
<tr>
<td>Scale-up time for new pods</td>
<td>3–5 minutes</td>
<td>30–60 seconds</td>
<td>-80%</td>
</tr>
</tbody></table>
<h2 id="heading-part-4-graviton-migration">Part 4: Graviton Migration</h2>
<p>AWS Graviton is Amazon's own ARM-based processor family, available across EC2 instance types with names ending in <code>g</code> — <code>m7g</code>, <code>c7g</code>, <code>r7g</code>, and so on.</p>
<p>Graviton instances are priced approximately 20% lower than equivalent Intel or AMD x86 instances. For most server-side workloads — Node.js, Python, Go, Java — they also deliver 20–40% better performance per dollar because the processor architecture is optimised specifically for these workload types.</p>
<p>You don't change your application code to use Graviton. You change the architecture flag in your container image build and the node selector in your Kubernetes deployment.</p>
<h3 id="heading-41-why-graviton-reduces-cost-without-reducing-performance">4.1 Why Graviton Reduces Cost Without Reducing Performance</h3>
<p>The first question to answer before migrating is whether your container images support ARM64. Most official images from Docker Hub ship as multi-architecture images. Your own application images need to be built for both architectures explicitly.</p>
<p>Check whether your images support ARM64:</p>
<pre><code class="language-bash"># Check if an image has an ARM64 manifest
docker manifest inspect your-registry/your-app:latest | jq '.manifests[].platform'
</code></pre>
<p>Expected output for a multi-arch image:</p>
<pre><code class="language-json">{"architecture": "amd64", "os": "linux"},
{"architecture": "arm64", "os": "linux", "variant": "v8"}
</code></pre>
<p>If <code>arm64</code> appears, the image is ready. If not, you need to build and push a multi-arch image first.</p>
<p>Build and push a multi-architecture image:</p>
<pre><code class="language-bash"># Build for both x86 and ARM in a single command using Docker Buildx
docker buildx create --use --name multi-arch-builder

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --tag your-registry/your-app:latest \
  --push \
  .
</code></pre>
<h3 id="heading-42-migrating-workloads-to-graviton">4.2 Migrating Workloads to Graviton</h3>
<p>With Karpenter already installed, Graviton migration is a single label change on your deployment. Karpenter provisions the appropriate ARM64 node automatically.</p>
<p>Here's the correct implementation:</p>
<pre><code class="language-yaml"># Good: nodeSelector directs the pod to Graviton nodes
# Karpenter provisions an arm64 node if one isn't already available
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-api
spec:
  template:
    spec:
      nodeSelector:
        kubernetes.io/arch: arm64   # Schedule exclusively on Graviton nodes
      containers:
        - name: api
          image: your-registry/payment-api:latest  # Must be multi-arch
</code></pre>
<p>Migrate gradually, starting with stateless services:</p>
<pre><code class="language-bash"># Step 1: Migrate one stateless service and monitor for 48 hours
kubectl patch deployment payment-api \
  -p '{"spec":{"template":{"spec":{"nodeSelector":{"kubernetes.io/arch":"arm64"}}}}}'

# Step 2: Watch for errors in the first 30 minutes
kubectl logs -l app=payment-api --tail=100 -f

# Step 3: Verify the pod is running on a Graviton node
# The NODE column should show a Graviton instance type (m7g, c7g, r7g)
kubectl get pods -l app=payment-api -o wide

# Step 4: After 48 hours of stable operation, migrate the next service
</code></pre>
<p>There are some situations where you shouldn't migrate to Graviton: GPU workloads, applications with native x86 binary dependencies, or any workload where you haven't yet built multi-arch images.</p>
<h3 id="heading-43-the-roi-of-graviton">4.3 The ROI of Graviton</h3>
<table>
<thead>
<tr>
<th>Workload Type</th>
<th>x86 Monthly Cost</th>
<th>Graviton Monthly Cost</th>
<th>Saving</th>
</tr>
</thead>
<tbody><tr>
<td>Web services (Node.js, Python)</td>
<td>$18,000</td>
<td>$14,400</td>
<td>$3,600/month</td>
</tr>
<tr>
<td>Data processing</td>
<td>$12,000</td>
<td>$9,600</td>
<td>$2,400/month</td>
</tr>
<tr>
<td>API services (Go, Java)</td>
<td>$8,000</td>
<td>$6,400</td>
<td>$1,600/month</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>$38,000</strong></td>
<td><strong>$30,400</strong></td>
<td><strong>$7,600/month</strong></td>
</tr>
</tbody></table>
<h2 id="heading-part-5-vpc-endpoints-for-data-transfer">Part 5: VPC Endpoints for Data Transfer</h2>
<h3 id="heading-51-the-nat-gateway-tax">5.1 The NAT Gateway Tax</h3>
<p>Every byte that travels from your EKS pods to an AWS service — S3, DynamoDB, ECR, SQS — goes through a NAT Gateway if you haven't configured VPC endpoints. NAT Gateway charges $0.045 per GB of data processed.</p>
<p>A busy EKS cluster pulling container images from ECR, writing to S3, and polling SQS queues can process hundreds of terabytes per month through NAT Gateway — generating thousands of dollars in charges for traffic that never actually left the AWS network.</p>
<p>Measure your current NAT Gateway cost before adding endpoints:</p>
<pre><code class="language-bash"># Get last month's NAT Gateway data processing charges
aws ce get-cost-and-usage \
  --time-period Start=\((date -d 'last month' +%Y-%m-01),End=\)(date +%Y-%m-01) \
  --granularity DAILY \
  --filter '{
    "Dimensions": {
      "Key": "USAGE_TYPE",
      "Values": ["NATGateway-Bytes"]
    }
  }' \
  --metrics UnblendedCost \
  --query 'ResultsByTime[*].{Date:TimePeriod.Start,Cost:Total.UnblendedCost.Amount}' \
  --output table
</code></pre>
<h3 id="heading-52-vpc-endpoints-the-fix-that-takes-30-minutes">5.2 VPC Endpoints — The Fix That Takes 30 Minutes</h3>
<p>A VPC endpoint creates a private connection between your VPC and an AWS service, routing traffic through the AWS backbone without touching the NAT Gateway. The data transfer becomes free. Each endpoint costs approximately \(0.01/hour — roughly \)7.20/month — far less than the NAT Gateway processing charges it replaces.</p>
<p>Here's the complete implementation for the four most common EKS traffic destinations:</p>
<pre><code class="language-bash"># Get your VPC ID and primary route table ID first
VPC_ID=$(aws eks describe-cluster --name your-cluster \
  --query 'cluster.resourcesVpcConfig.vpcId' --output text)

ROUTE_TABLE_ID=$(aws ec2 describe-route-tables \
  --filters Name=vpc-id,Values=$VPC_ID Name=association.main,Values=true \
  --query 'RouteTables[0].RouteTableId' --output text)

echo "VPC: \(VPC_ID | Route Table: \)ROUTE_TABLE_ID"

# S3 gateway endpoint — free to create, eliminates all S3 traffic through NAT
aws ec2 create-vpc-endpoint \
  --vpc-id $VPC_ID \
  --service-name com.amazonaws.us-east-1.s3 \
  --route-table-ids $ROUTE_TABLE_ID

# DynamoDB gateway endpoint — also free, same mechanism as S3
aws ec2 create-vpc-endpoint \
  --vpc-id $VPC_ID \
  --service-name com.amazonaws.us-east-1.dynamodb \
  --route-table-ids $ROUTE_TABLE_ID

# ECR API interface endpoint — eliminates NAT charges on image pulls
aws ec2 create-vpc-endpoint \
  --vpc-id $VPC_ID \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.us-east-1.ecr.api \
  --subnet-ids $(aws ec2 describe-subnets \
    --filters Name=vpc-id,Values=$VPC_ID Name=tag:Tier,Values=private \
    --query 'Subnets[*].SubnetId' --output text)

# ECR Docker endpoint — required alongside ECR API for complete image pull coverage
aws ec2 create-vpc-endpoint \
  --vpc-id $VPC_ID \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.us-east-1.ecr.dkr \
  --subnet-ids $(aws ec2 describe-subnets \
    --filters Name=vpc-id,Values=$VPC_ID Name=tag:Tier,Values=private \
    --query 'Subnets[*].SubnetId' --output text)
</code></pre>
<p>The Terraform module that creates all four endpoints in a single <code>apply</code> is in <code>terraform/vpc-endpoints/</code> in the <a href="https://github.com/aayostem/eks-cost-optimization/tree/main/terraform/vpc-endpoints">companion repo</a>.</p>
<p>Verify that the endpoints are routing traffic correctly:</p>
<pre><code class="language-bash">aws ec2 describe-vpc-endpoints \
  --filters Name=vpc-id,Values=$VPC_ID \
  --query 'VpcEndpoints[*].{Service:ServiceName,State:State,Type:VpcEndpointType}' \
  --output table
# Expected: all endpoints showing State=available
</code></pre>
<h3 id="heading-53-the-roi-of-vpc-endpoints">5.3 The ROI of VPC Endpoints</h3>
<table>
<thead>
<tr>
<th>Service</th>
<th>Before (Through NAT)</th>
<th>After (VPC Endpoint)</th>
<th>Monthly Saving</th>
</tr>
</thead>
<tbody><tr>
<td>S3 data transfer</td>
<td>$4,500</td>
<td>$0</td>
<td>$4,500</td>
</tr>
<tr>
<td>ECR image pulls</td>
<td>$800</td>
<td>$0</td>
<td>$800</td>
</tr>
<tr>
<td>DynamoDB queries</td>
<td>$1,200</td>
<td>$0</td>
<td>$1,200</td>
</tr>
<tr>
<td>Endpoint cost</td>
<td>—</td>
<td>$29 (4 endpoints)</td>
<td>-$29</td>
</tr>
<tr>
<td><strong>Net saving</strong></td>
<td></td>
<td></td>
<td><strong>$6,471/month</strong></td>
</tr>
</tbody></table>
<h2 id="heading-part-6-ebs-volume-optimisation">Part 6: EBS Volume Optimisation</h2>
<h3 id="heading-61-the-gp2-to-gp3-migration">6.1 The gp2 to gp3 Migration</h3>
<p>EBS gp2 volumes price their IOPS based on storage size — 3 IOPS per GB, with a 100 IOPS minimum. EBS gp3 volumes provide 3,000 IOPS baseline regardless of size, and cost 20% less per GB. The migration runs online with no downtime.</p>
<p>Find and migrate all gp2 volumes:</p>
<pre><code class="language-bash"># Step 1: List all gp2 volumes and their sizes
aws ec2 describe-volumes \
  --filters Name=volume-type,Values=gp2 \
  --query 'Volumes[*].{ID:VolumeId,Size:Size,State:State}' \
  --output table

# Step 2: Migrate each gp2 volume to gp3 — no instance stop required
# The modify operation runs online while the volume stays attached and in use
aws ec2 describe-volumes \
  --filters Name=volume-type,Values=gp2 \
  --query 'Volumes[*].VolumeId' \
  --output text | tr '\t' '\n' | while read vol; do
    echo "Migrating $vol from gp2 to gp3..."
    aws ec2 modify-volume \
      --volume-id $vol \
      --volume-type gp3
done

# Step 3: Verify all volumes are now gp3
aws ec2 describe-volumes \
  --filters Name=volume-type,Values=gp2 \
  --query 'Volumes[*].VolumeId' \
  --output text
# Expected: empty output — zero gp2 volumes remaining
</code></pre>
<h3 id="heading-62-finding-and-removing-orphaned-volumes-and-snapshots">6.2 Finding and Removing Orphaned Volumes and Snapshots</h3>
<p>When Kubernetes PersistentVolumeClaims are deleted, the underlying EBS volumes sometimes aren't cleaned up. They keep running — and billing — indefinitely.</p>
<pre><code class="language-bash"># Find unattached EBS volumes — status=available means not attached to any instance
aws ec2 describe-volumes \
  --filters Name=status,Values=available \
  --query 'Volumes[*].{ID:VolumeId,Size:Size,Created:CreateTime}' \
  --output table

# Find EBS snapshots older than 90 days
aws ec2 describe-snapshots \
  --owner-ids self \
  --query "Snapshots[?StartTime&lt;='$(date -d '90 days ago' --iso-8601=seconds)'].[SnapshotId,StartTime,VolumeSize]" \
  --output table
</code></pre>
<p>Before deleting any snapshot, cross-reference with your RDS automated backup schedule to confirm it's not the only backup for a production database.</p>
<h3 id="heading-63-the-roi-of-ebs-optimisation">6.3 The ROI of EBS Optimisation</h3>
<table>
<thead>
<tr>
<th>Resource</th>
<th>Before</th>
<th>After</th>
<th>Monthly Saving</th>
</tr>
</thead>
<tbody><tr>
<td>gp2 → gp3 migration (1TB total)</td>
<td>$102</td>
<td>$72</td>
<td>$30</td>
</tr>
<tr>
<td>Unattached volumes removed (50 × 100GB)</td>
<td>$500</td>
<td>$0</td>
<td>$500</td>
</tr>
<tr>
<td>Old snapshots cleaned (500GB)</td>
<td>$25</td>
<td>$0</td>
<td>$25</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>$627</strong></td>
<td><strong>$72</strong></td>
<td><strong>$555/month</strong></td>
</tr>
</tbody></table>
<h2 id="heading-part-7-load-balancer-consolidation">Part 7: Load Balancer Consolidation</h2>
<h3 id="heading-71-the-problem-one-load-balancer-per-service">7.1 The Problem — One Load Balancer Per Service</h3>
<p>Many teams create a separate <code>LoadBalancer</code> Service for every microservice. On AWS, each Application Load Balancer costs approximately \(16.20/month base charge plus \)0.008/LCU-hour for traffic processed. At 20 microservices, that's $324/month before a single request is processed.</p>
<p>Here's the incorrect approach:</p>
<pre><code class="language-yaml"># Bad: This creates a dedicated AWS ALB every time it's applied
# 20 microservices = 20 ALBs = $324+/month before any traffic charges
apiVersion: v1
kind: Service
metadata:
  name: payment-api
spec:
  type: LoadBalancer   # Creates a dedicated ALB
  ports:
  - port: 80
    targetPort: 8080
</code></pre>
<h3 id="heading-72-the-fix-shared-ingress-controller">7.2 The Fix — Shared Ingress Controller</h3>
<p>An Ingress controller is a Kubernetes component that runs as a pod inside your cluster and programs a single external load balancer to route traffic to multiple services based on hostname and URL path. Instead of one AWS Application Load Balancer per microservice, you get one ALB total — with path-based routing directing each request to the right backend service. The result is the same routing behaviour at a fraction of the cost.</p>
<p>Here's the correct implementation:</p>
<pre><code class="language-yaml"># Good: One Ingress resource routes all external traffic
# The AWS Load Balancer Controller creates one ALB for all services listed here
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shared-ingress
  namespace: production
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
    alb.ingress.kubernetes.io/ssl-redirect: "443"
spec:
  rules:
  - host: api.company.com
    http:
      paths:
      - path: /payments
        pathType: Prefix
        backend:
          service:
            name: payment-service
            port:
              number: 8080
      - path: /users
        pathType: Prefix
        backend:
          service:
            name: user-service
            port:
              number: 8080
  - host: dashboard.company.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: dashboard-service
            port:
              number: 3000
  tls:
  - hosts:
    - api.company.com
    - dashboard.company.com
    secretName: tls-wildcard-cert
</code></pre>
<p>Verify the Ingress is provisioned and the ALB DNS name is assigned:</p>
<pre><code class="language-bash"># Watch until the ADDRESS column shows the ALB DNS name (typically 2–3 minutes)
kubectl get ingress shared-ingress -n production -w
</code></pre>
<p>The cost difference:</p>
<table>
<thead>
<tr>
<th>Approach</th>
<th>Load balancers</th>
<th>Monthly cost</th>
</tr>
</thead>
<tbody><tr>
<td>LoadBalancer Service per microservice (20 services)</td>
<td>20 ALBs</td>
<td>~$400/month</td>
</tr>
<tr>
<td>Single Ingress controller</td>
<td>1 ALB</td>
<td>~$27/month</td>
</tr>
<tr>
<td><strong>Monthly saving</strong></td>
<td></td>
<td><strong>~$373/month</strong></td>
</tr>
</tbody></table>
<p>The shared Ingress manifest is in <code>k8s/ingress/</code> in the <a href="https://github.com/aayostem/eks-cost-optimization/tree/main/k8s/ingress">companion repo</a>.</p>
<h2 id="heading-the-complete-7-step-sequence">The Complete 7-Step Sequence</h2>
<table>
<thead>
<tr>
<th>Step</th>
<th>Action</th>
<th>Time to Implement</th>
<th>Expected Monthly Saving</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Right-size pod resource requests (VPA)</td>
<td>1 week</td>
<td>$15,600</td>
</tr>
<tr>
<td>2</td>
<td>Install Karpenter with consolidation</td>
<td>1 week</td>
<td>$8,400</td>
</tr>
<tr>
<td>3</td>
<td>Move staging and dev to Spot</td>
<td>1 week</td>
<td>$11,200</td>
</tr>
<tr>
<td>4</td>
<td>Migrate compatible workloads to Graviton</td>
<td>2 weeks</td>
<td>$7,600</td>
</tr>
<tr>
<td>5</td>
<td>Add VPC endpoints for S3, ECR, DynamoDB</td>
<td>1 day</td>
<td>$6,471</td>
</tr>
<tr>
<td>6</td>
<td>Migrate gp2 to gp3 and delete orphaned volumes</td>
<td>1 day</td>
<td>$555</td>
</tr>
<tr>
<td>7</td>
<td>Consolidate load balancers with shared Ingress</td>
<td>1 day</td>
<td>$373</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td></td>
<td><strong>3–4 weeks</strong></td>
<td><strong>$49,799/month</strong></td>
</tr>
</tbody></table>
<p>Annual saving at this rate: <strong>$597,588</strong>. Engineering time required: one engineer, one sprint per step.</p>
<h2 id="heading-best-practices-for-eks-cost-optimisation">Best Practices for EKS Cost Optimisation</h2>
<p>✅ <strong>Do:</strong> Right-size pod resource requests before any other optimisation. Every subsequent step depends on accurate requests.</p>
<p>✅ <strong>Do:</strong> Implement Karpenter with <code>consolidationPolicy: WhenUnderutilized</code>. Let it continuously optimise your node count automatically.</p>
<p>✅ <strong>Do:</strong> Move staging and development workloads to Spot. 60–90% savings for workloads that tolerate interruption.</p>
<p>✅ <strong>Do:</strong> Migrate compatible workloads to Graviton. Most web services and APIs run without code changes.</p>
<p>✅ <strong>Do:</strong> Add VPC endpoints for S3, DynamoDB, and ECR before reviewing data transfer costs.</p>
<p>✅ <strong>Do:</strong> Migrate gp2 volumes to gp3. It's online, zero downtime, and immediately 20% cheaper.</p>
<p>✅ <strong>Do:</strong> Use a single shared Ingress controller for all external traffic instead of per-service load balancers.</p>
<p>❌ <strong>Don't:</strong> Buy Savings Plans before completing steps 1–6. You'll lock in waste for 1–3 years.</p>
<p>❌ <strong>Don't:</strong> Use static node groups with Cluster Autoscaler when your workload mix changes. Karpenter handles this dynamically.</p>
<p>❌ <strong>Don't:</strong> Run staging and development environments on On-Demand instances. Spot interruptions are manageable, but the cost difference is not.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><a href="https://karpenter.sh/docs/"><strong>Karpenter Documentation</strong></a> — Official NodePool configuration reference and installation guide</p>
</li>
<li><p><a href="https://github.com/aws/aws-graviton-getting-started"><strong>AWS Graviton Getting Started Guide</strong></a> — Language-specific compatibility notes and migration guidance from AWS</p>
</li>
<li><p><a href="https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler"><strong>Vertical Pod Autoscaler GitHub</strong></a> — VPA installation and configuration documentation</p>
</li>
<li><p><a href="https://docs.aws.amazon.com/vpc/latest/privatelink/vpc-endpoints.html"><strong>AWS VPC Endpoints Documentation</strong></a> — Complete list of available VPC endpoints and configuration options</p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/requesting-ebs-volume-modifications.html"><strong>EBS Volume Modification Documentation</strong></a> — AWS guide for online volume type migration with zero downtime</p>
</li>
<li><p><a href="https://kubernetes-sigs.github.io/aws-load-balancer-controller/"><strong>AWS Load Balancer Controller</strong></a> — Official documentation for the Ingress controller that provisions AWS ALBs</p>
</li>
<li><p><a href="https://docs.aws.amazon.com/cost-management/latest/APIReference/API_GetCostAndUsage.html"><strong>AWS Cost Explorer API Reference</strong></a> — Full reference for the cost breakdown commands used throughout this guide</p>
</li>
<li><p><a href="https://aws.github.io/aws-eks-best-practices/cost_optimization/cfm_framework/"><strong>EKS Best Practices Guide — Cost Optimisation</strong></a> — AWS's official EKS cost optimisation framework</p>
</li>
<li><p><a href="https://github.com/aayostem/eks-cost-optimization"><strong>Companion Repository</strong></a> — All Terraform modules, NodePool templates, VPA manifests, and automation scripts from this guide</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Optimize Your Docker Build Cache & Cut Your CI/CD Pipeline Times by 80% ]]>
                </title>
                <description>
                    <![CDATA[ Every developer has been there. You push a one-line fix, grab your coffee, and wait. And wait. Twelve minutes later, your Docker image finishes rebuilding from scratch because something about the cach ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-optimize-your-docker-build-cache/</link>
                <guid isPermaLink="false">69bb1e218c55d6eefb64955f</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Balajee Asish Brahmandam ]]>
                </dc:creator>
                <pubDate>Wed, 18 Mar 2026 21:50:25 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/9a5ca46f-c571-4d38-90b5-3c6d7d22c00f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every developer has been there. You push a one-line fix, grab your coffee, and wait. And wait. Twelve minutes later, your Docker image finishes rebuilding from scratch because something about the cache broke again.</p>
<p>I spent a good chunk of last year debugging slow Docker builds across multiple teams. The pattern was always the same: builds that should take two minutes were eating up fifteen, and nobody knew why. The fix turned out to be surprisingly systematic once I understood what was actually happening under the hood.</p>
<p>This guide walks you through exactly how to fix slow Docker builds, step by step. We'll start with how the cache actually works, then tear apart the most common mistakes, and finish with production-ready patterns you can copy into your projects today.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-how-docker-build-cache-actually-works">How Docker Build Cache Actually Works</a></p>
<ul>
<li><p><a href="#heading-how-cache-keys-are-computed">How Cache Keys Are Computed</a></p>
</li>
<li><p><a href="#heading-the-cache-chain-rule">The Cache Chain Rule</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-identify-common-cache-busting-mistakes">How to Identify Common Cache-Busting Mistakes</a></p>
<ul>
<li><p><a href="#heading-mistake-1-copying-everything-too-early">Mistake 1: Copying Everything Too Early</a></p>
</li>
<li><p><a href="#heading-mistake-2-not-separating-dependency-files">Mistake 2: Not Separating Dependency Files</a></p>
</li>
<li><p><a href="#heading-mistake-3-using-add-instead-of-copy">Mistake 3: Using ADD Instead of COPY</a></p>
</li>
<li><p><a href="#heading-mistake-4-splitting-apt-get-update-and-install">Mistake 4: Splitting apt-get update and install</a></p>
</li>
<li><p><a href="#heading-mistake-5-embedding-timestamps-or-git-hashes-too-early">Mistake 5: Embedding Timestamps or Git Hashes Too Early</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-structure-your-dockerfile-for-maximum-cache-reuse">How to Structure Your Dockerfile for Maximum Cache Reuse</a></p>
<ul>
<li><p><a href="#heading-step-1-apply-the-dependency-first-pattern">Step 1: Apply the Dependency-First Pattern</a></p>
</li>
<li><p><a href="#heading-step-2-add-an-aggressive-dockerignore">Step 2: Add an Aggressive .dockerignore</a></p>
</li>
<li><p><a href="#heading-step-3-use-multi-stage-builds">Step 3: Use Multi-Stage Builds</a></p>
</li>
<li><p><a href="#heading-step-4-order-layers-by-change-frequency">Step 4: Order Layers by Change Frequency</a></p>
</li>
<li><p><a href="#heading-step-5-use-buildkit-mount-caches">Step 5: Use BuildKit Mount Caches</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-set-up-cicd-cache-backends">How to Set Up CI/CD Cache Backends</a></p>
<ul>
<li><p><a href="#heading-option-a-registry-based-cache">Option A: Registry-Based Cache</a></p>
</li>
<li><p><a href="#heading-option-b-github-actions-cache">Option B: GitHub Actions Cache</a></p>
</li>
<li><p><a href="#heading-option-c-s3-or-cloud-storage">Option C: S3 or Cloud Storage</a></p>
</li>
<li><p><a href="#heading-option-d-local-cache-with-persistent-runners">Option D: Local Cache with Persistent Runners</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-implement-advanced-cache-patterns">How to Implement Advanced Cache Patterns</a></p>
<ul>
<li><p><a href="#heading-parallel-build-stages">Parallel Build Stages</a></p>
</li>
<li><p><a href="#heading-cache-warming-for-feature-branches">Cache Warming for Feature Branches</a></p>
</li>
<li><p><a href="#heading-selective-cache-invalidation-with-build-args">Selective Cache Invalidation with Build Args</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-measure-your-improvements">How to Measure Your Improvements</a></p>
<ul>
<li><p><a href="#heading-the-four-scenarios-to-benchmark">The Four Scenarios to Benchmark</a></p>
</li>
<li><p><a href="#heading-real-world-before-and-after-numbers">Real-World Before and After Numbers</a></p>
</li>
<li><p><a href="#heading-how-to-check-cache-hit-rates">How to Check Cache Hit Rates</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-complete-optimized-dockerfile-examples">Complete Optimized Dockerfile Examples</a></p>
<ul>
<li><p><a href="#heading-nodejs-full-stack-app">Node.js Full-Stack App</a></p>
</li>
<li><p><a href="#heading-python-fastapi-app">Python FastAPI App</a></p>
</li>
<li><p><a href="#heading-go-microservice">Go Microservice</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-troubleshooting-guide">Troubleshooting Guide</a></p>
</li>
<li><p><a href="#heading-quick-reference-checklist">Quick-Reference Checklist</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along, you'll need:</p>
<ul>
<li><p>A working Docker installation (Docker Desktop or Docker Engine 20.10+)</p>
</li>
<li><p>Basic comfort with writing Dockerfiles</p>
</li>
<li><p>Access to a CI/CD system like GitHub Actions, GitLab CI, or Jenkins</p>
</li>
</ul>
<h2 id="heading-how-docker-build-cache-actually-works">How Docker Build Cache Actually Works</h2>
<p>Every instruction in a Dockerfile produces a <strong>layer</strong>. Docker stores these layers and reuses them when it detects nothing has changed. That's the cache. Simple enough in theory, but the details matter a lot.</p>
<h3 id="heading-how-cache-keys-are-computed">How Cache Keys Are Computed</h3>
<p>Different instructions compute their cache keys differently:</p>
<table>
<thead>
<tr>
<th>Instruction</th>
<th>Cache Key Based On</th>
<th>What Breaks It</th>
</tr>
</thead>
<tbody><tr>
<td><code>RUN</code></td>
<td>The exact command string</td>
<td>Any change to the command text</td>
</tr>
<tr>
<td><code>COPY</code> / <code>ADD</code></td>
<td>File checksums of the source content</td>
<td>Any modification to the copied files</td>
</tr>
<tr>
<td><code>ENV</code> / <code>ARG</code></td>
<td>The variable name and value</td>
<td>Changing the value</td>
</tr>
<tr>
<td><code>FROM</code></td>
<td>The base image digest</td>
<td>A new version of the base image</td>
</tr>
</tbody></table>
<h3 id="heading-the-cache-chain-rule">The Cache Chain Rule</h3>
<p>Here's the thing most people miss: <strong>Docker cache is sequential.</strong> If any layer's cache gets invalidated, every layer after it rebuilds from scratch, even if those later layers haven't changed at all.</p>
<p>Picture a row of dominoes. Knock one over in the middle and everything after it goes down too. This is why the order of instructions in your Dockerfile is so important.</p>
<blockquote>
<p><strong>Key insight:</strong> The single most impactful optimization you can make is reordering your Dockerfile so that the stuff that changes most often comes last.</p>
</blockquote>
<h2 id="heading-how-to-identify-common-cache-busting-mistakes">How to Identify Common Cache-Busting Mistakes</h2>
<p>Before we fix anything, let's look at what's probably breaking your cache right now. I've seen these patterns in almost every unoptimized Dockerfile I've reviewed.</p>
<h3 id="heading-mistake-1-copying-everything-too-early">Mistake 1: Copying Everything Too Early</h3>
<p>This is the big one. Putting <code>COPY . .</code> near the top of the Dockerfile, before installing dependencies, means that <em>any</em> file change in your project invalidates the cache from that point forward. Changed a README? Cool, now your dependencies reinstall.</p>
<pre><code class="language-dockerfile"># BAD: Any file change invalidates the dependency install
FROM node:20-alpine
WORKDIR /app
COPY . .                    # Cache busted on every commit
RUN npm ci                  # Reinstalls every single time
RUN npm run build
</code></pre>
<h3 id="heading-mistake-2-not-separating-dependency-files">Mistake 2: Not Separating Dependency Files</h3>
<p>Your dependency manifests (<code>package.json</code>, <code>requirements.txt</code>, <code>go.mod</code>, <code>Gemfile</code>) change way less often than your source code. If you don't copy them separately, you're reinstalling all dependencies every time you touch a source file.</p>
<h3 id="heading-mistake-3-using-add-instead-of-copy">Mistake 3: Using ADD Instead of COPY</h3>
<p><code>ADD</code> has special behaviors like auto-extracting archives and fetching remote URLs. Those features make its cache behavior unpredictable. Stick with <code>COPY</code> unless you specifically need archive extraction.</p>
<h3 id="heading-mistake-4-splitting-apt-get-update-and-install">Mistake 4: Splitting apt-get update and install</h3>
<p>When you put <code>apt-get update</code> and <code>apt-get install</code> in separate <code>RUN</code> commands, the update step gets cached with stale package indexes. Then the install step fails or grabs outdated packages.</p>
<pre><code class="language-dockerfile"># BAD: Stale package index
RUN apt-get update
RUN apt-get install -y curl    # May fail with stale index

# GOOD: Always combine them
RUN apt-get update &amp;&amp; apt-get install -y curl &amp;&amp; rm -rf /var/lib/apt/lists/*
</code></pre>
<h3 id="heading-mistake-5-embedding-timestamps-or-git-hashes-too-early">Mistake 5: Embedding Timestamps or Git Hashes Too Early</h3>
<p>Injecting build-time variables like timestamps or git commit hashes via <code>ARG</code> or <code>ENV</code> early in the Dockerfile invalidates the cache on every single build. Move these to the very last layer.</p>
<blockquote>
<p>⚠️ <strong>Watch out for this:</strong> CI/CD systems often inject variables like <code>BUILD_NUMBER</code> or <code>GIT_SHA</code> as build args automatically. If those <code>ARG</code> declarations sit near the top, your cache is toast on every run.</p>
</blockquote>
<h2 id="heading-how-to-structure-your-dockerfile-for-maximum-cache-reuse">How to Structure Your Dockerfile for Maximum Cache Reuse</h2>
<p>Now let's fix those mistakes. These five steps, applied in order, will get you most of the way to an optimized build.</p>
<h3 id="heading-step-1-apply-the-dependency-first-pattern">Step 1: Apply the Dependency-First Pattern</h3>
<p>Copy only the dependency manifests first, install, and then copy the rest of the source code. This one change alone can cut your build times in half.</p>
<pre><code class="language-dockerfile"># GOOD: Dependency-first pattern for Node.js
FROM node:20-alpine
WORKDIR /app

# Copy ONLY dependency files
COPY package.json package-lock.json ./

# Install dependencies (cached unless package files change)
RUN npm ci --production

# Copy source code (only this layer rebuilds on code changes)
COPY . .

# Build
RUN npm run build
</code></pre>
<p>The same idea works across every language:</p>
<table>
<thead>
<tr>
<th>Language</th>
<th>Copy First</th>
<th>Install Command</th>
</tr>
</thead>
<tbody><tr>
<td>Node.js</td>
<td><code>package.json</code>, <code>package-lock.json</code></td>
<td><code>npm ci</code></td>
</tr>
<tr>
<td>Python</td>
<td><code>requirements.txt</code> or <code>pyproject.toml</code></td>
<td><code>pip install -r requirements.txt</code></td>
</tr>
<tr>
<td>Go</td>
<td><code>go.mod</code>, <code>go.sum</code></td>
<td><code>go mod download</code></td>
</tr>
<tr>
<td>Rust</td>
<td><code>Cargo.toml</code>, <code>Cargo.lock</code></td>
<td><code>cargo fetch</code></td>
</tr>
<tr>
<td>Java (Maven)</td>
<td><code>pom.xml</code></td>
<td><code>mvn dependency:go-offline</code></td>
</tr>
<tr>
<td>Ruby</td>
<td><code>Gemfile</code>, <code>Gemfile.lock</code></td>
<td><code>bundle install</code></td>
</tr>
</tbody></table>
<h3 id="heading-step-2-add-an-aggressive-dockerignore">Step 2: Add an Aggressive .dockerignore</h3>
<p>A <code>.dockerignore</code> file keeps irrelevant files out of the build context. Fewer files in the context means fewer things that can break your cache.</p>
<pre><code class="language-plaintext"># .dockerignore
.git
node_modules
dist
*.md
*.log
.env*
docker-compose*.yml
Dockerfile*
.github
tests
coverage
__pycache__
</code></pre>
<h3 id="heading-step-3-use-multi-stage-builds">Step 3: Use Multi-Stage Builds</h3>
<p>Multi-stage builds let you use a full development image for compiling, then copy only the finished artifacts into a slim runtime image. You get smaller images, better security, and improved cache performance because build tools and intermediate files don't carry over.</p>
<pre><code class="language-dockerfile"># Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
</code></pre>
<h3 id="heading-step-4-order-layers-by-change-frequency">Step 4: Order Layers by Change Frequency</h3>
<p>Think of your Dockerfile as a stack. Put the boring, stable stuff at the top and the volatile stuff at the bottom:</p>
<ol>
<li><p>Base image and system dependencies (rarely change)</p>
</li>
<li><p>Language runtime configuration (occasionally change)</p>
</li>
<li><p>Application dependencies (change when you add or remove packages)</p>
</li>
<li><p>Source code (changes on every commit)</p>
</li>
<li><p>Build-time metadata like git hash or version labels (changes every build)</p>
</li>
</ol>
<h3 id="heading-step-5-use-buildkit-mount-caches">Step 5: Use BuildKit Mount Caches</h3>
<p>Docker BuildKit supports <code>RUN --mount=type=cache</code>, which mounts a persistent cache directory that survives across builds. This is a game-changer for package managers that maintain their own download caches.</p>
<pre><code class="language-dockerfile"># syntax=docker/dockerfile:1

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .

# Mount pip cache so downloads persist across builds
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

COPY . .
</code></pre>
<p>The best part: mount caches persist even when the layer itself gets invalidated. So if you add one new package, pip only downloads that one package instead of re-fetching everything.</p>
<p>Here are the common cache targets for popular package managers:</p>
<table>
<thead>
<tr>
<th>Package Manager</th>
<th>Cache Target</th>
</tr>
</thead>
<tbody><tr>
<td>pip</td>
<td><code>/root/.cache/pip</code></td>
</tr>
<tr>
<td>npm</td>
<td><code>/root/.npm</code></td>
</tr>
<tr>
<td>yarn</td>
<td><code>/usr/local/share/.cache/yarn</code></td>
</tr>
<tr>
<td>go</td>
<td><code>/go/pkg/mod</code></td>
</tr>
<tr>
<td>apt</td>
<td><code>/var/cache/apt</code></td>
</tr>
<tr>
<td>maven</td>
<td><code>/root/.m2/repository</code></td>
</tr>
</tbody></table>
<h2 id="heading-how-to-set-up-cicd-cache-backends">How to Set Up CI/CD Cache Backends</h2>
<p>Here's where things get tricky. Your local Docker cache works great on your laptop because the layers persist between builds. But CI/CD runners are usually ephemeral: each job starts with a totally empty cache. Without explicit cache configuration, every CI build is a cold build.</p>
<h3 id="heading-option-a-registry-based-cache">Option A: Registry-Based Cache</h3>
<p>BuildKit can push and pull cache layers from a container registry. This is the most portable approach and works with any CI system.</p>
<pre><code class="language-bash">docker buildx build \
  --cache-from type=registry,ref=myregistry.io/myapp:buildcache \
  --cache-to type=registry,ref=myregistry.io/myapp:buildcache,mode=max \
  --tag myregistry.io/myapp:latest \
  --push .
</code></pre>
<blockquote>
<p>💡 <strong>Use</strong> <code>mode=max</code> to cache all layers including intermediate build stages. The default <code>mode=min</code> only caches layers in the final stage, which means your build stage layers get thrown away.</p>
</blockquote>
<h3 id="heading-option-b-github-actions-cache">Option B: GitHub Actions Cache</h3>
<p>If you're on GitHub Actions, there's native integration with BuildKit through the GitHub Actions cache API. It's fast and requires minimal setup.</p>
<pre><code class="language-yaml"># .github/workflows/build.yml
- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Build and push
  uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    tags: myregistry.io/myapp:latest
    cache-from: type=gha
    cache-to: type=gha,mode=max
</code></pre>
<h3 id="heading-option-c-s3-or-cloud-storage">Option C: S3 or Cloud Storage</h3>
<p>For teams on AWS, GCP, or Azure, cloud object storage makes a solid cache backend. It's fast, persistent, and works across any CI system.</p>
<pre><code class="language-bash">docker buildx build \
  --cache-from type=s3,region=us-east-1,bucket=my-docker-cache,name=myapp \
  --cache-to type=s3,region=us-east-1,bucket=my-docker-cache,name=myapp,mode=max \
  --tag myapp:latest .
</code></pre>
<h3 id="heading-option-d-local-cache-with-persistent-runners">Option D: Local Cache with Persistent Runners</h3>
<p>If your CI runners have persistent storage (self-hosted runners, GitLab runners with shared volumes), you can export cache to a local directory.</p>
<pre><code class="language-bash">docker buildx build \
  --cache-from type=local,src=/ci-cache/myapp \
  --cache-to type=local,dest=/ci-cache/myapp,mode=max \
  --tag myapp:latest .
</code></pre>
<h2 id="heading-how-to-implement-advanced-cache-patterns">How to Implement Advanced Cache Patterns</h2>
<p>Once you've nailed the basics, these patterns can squeeze out even more performance.</p>
<h3 id="heading-parallel-build-stages">Parallel Build Stages</h3>
<p>BuildKit builds independent stages in parallel. If your app has a frontend and a backend that don't depend on each other during build, split them into separate stages and let BuildKit run them simultaneously.</p>
<pre><code class="language-dockerfile"># These stages build in parallel
FROM node:20-alpine AS frontend
WORKDIR /frontend
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build

FROM python:3.12-slim AS backend
WORKDIR /backend
COPY backend/requirements.txt .
RUN pip install -r requirements.txt
COPY backend/ .

# Final stage combines both
FROM python:3.12-slim
COPY --from=backend /backend /app
COPY --from=frontend /frontend/dist /app/static
CMD ["python", "/app/main.py"]
</code></pre>
<h3 id="heading-cache-warming-for-feature-branches">Cache Warming for Feature Branches</h3>
<p>Feature branches often start with a cold cache because they diverge from main. You can warm the cache by specifying multiple <code>--cache-from</code> sources. Docker checks them in order.</p>
<pre><code class="language-bash">docker buildx build \
  --cache-from type=registry,ref=registry.io/app:cache-${BRANCH} \
  --cache-from type=registry,ref=registry.io/app:cache-main \
  --cache-to type=registry,ref=registry.io/app:cache-${BRANCH},mode=max \
  --tag registry.io/app:${BRANCH} .
</code></pre>
<p>If the branch cache hits, Docker uses it. If not, it falls back to main's cache, which usually shares most layers. This makes a massive difference for short-lived branches.</p>
<h3 id="heading-selective-cache-invalidation-with-build-args">Selective Cache Invalidation with Build Args</h3>
<p>You can use <code>ARG</code> instructions as cache boundaries. Anything above the <code>ARG</code> stays cached, while anything below it rebuilds when the arg value changes.</p>
<pre><code class="language-dockerfile">FROM node:20-alpine
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

# This ARG only invalidates layers below it
ARG CACHE_BUST_CODE=1
COPY . .
RUN npm run build

# This ARG only invalidates the label
ARG GIT_SHA=unknown
LABEL git.sha=$GIT_SHA
</code></pre>
<h2 id="heading-how-to-measure-your-improvements">How to Measure Your Improvements</h2>
<p>Optimization without measurement is just guessing. Here's how to actually prove your changes are working.</p>
<h3 id="heading-the-four-scenarios-to-benchmark">The Four Scenarios to Benchmark</h3>
<p>Run each scenario at least three times and take the median:</p>
<ol>
<li><p><strong>Cold build:</strong> No cache at all (first build or after <code>docker builder prune</code>)</p>
</li>
<li><p><strong>Warm build:</strong> No changes, full cache hit</p>
</li>
<li><p><strong>Code change:</strong> Only source code modified</p>
</li>
<li><p><strong>Dependency change:</strong> Package manifest modified</p>
</li>
</ol>
<h3 id="heading-real-world-before-and-after-numbers">Real-World Before and After Numbers</h3>
<p>Here's what I saw on a mid-sized Node.js project after applying the techniques from this guide:</p>
<table>
<thead>
<tr>
<th>Scenario</th>
<th>Before</th>
<th>After</th>
<th>Improvement</th>
</tr>
</thead>
<tbody><tr>
<td>Cold build</td>
<td>12 min 34 sec</td>
<td>8 min 10 sec</td>
<td>35%</td>
</tr>
<tr>
<td>Warm build (no changes)</td>
<td>12 min 34 sec</td>
<td>14 sec</td>
<td>98%</td>
</tr>
<tr>
<td>Code change only</td>
<td>12 min 34 sec</td>
<td>1 min 52 sec</td>
<td>85%</td>
</tr>
<tr>
<td>Dependency change</td>
<td>12 min 34 sec</td>
<td>4 min 20 sec</td>
<td>65%</td>
</tr>
</tbody></table>
<p>The "before" column is the same for all rows because without cache optimization, every build was essentially a cold build. That 85% improvement on code-only changes is the number that matters most, since that's what happens on the vast majority of commits.</p>
<h3 id="heading-how-to-check-cache-hit-rates">How to Check Cache Hit Rates</h3>
<p>Set <code>BUILDKIT_PROGRESS=plain</code> to get detailed output showing which layers hit cache:</p>
<pre><code class="language-bash">BUILDKIT_PROGRESS=plain docker buildx build . 2&gt;&amp;1 | grep -E 'CACHED|DONE'
</code></pre>
<p>Look for the <code>CACHED</code> prefix on layers. Your goal is to see <code>CACHED</code> on everything except the layers that actually needed to change.</p>
<h2 id="heading-complete-optimized-dockerfile-examples">Complete Optimized Dockerfile Examples</h2>
<p>Here are production-ready Dockerfiles you can adapt for your own projects.</p>
<h3 id="heading-nodejs-full-stack-app">Node.js Full-Stack App</h3>
<pre><code class="language-dockerfile"># syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 appgroup \
    &amp;&amp; adduser --system --uid 1001 appuser
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
COPY package.json ./
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
</code></pre>
<h3 id="heading-python-fastapi-app">Python FastAPI App</h3>
<pre><code class="language-dockerfile"># syntax=docker/dockerfile:1
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install --user -r requirements.txt

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
</code></pre>
<h3 id="heading-go-microservice">Go Microservice</h3>
<pre><code class="language-dockerfile"># syntax=docker/dockerfile:1
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 go build -ldflags='-s -w' -o /app/server ./cmd/server

FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
</code></pre>
<h2 id="heading-troubleshooting-guide">Troubleshooting Guide</h2>
<p>When things go wrong, check this table first:</p>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Likely Cause</th>
<th>Fix</th>
</tr>
</thead>
<tbody><tr>
<td>All layers rebuild every time</td>
<td><code>COPY . .</code> is too early, or <code>.dockerignore</code> is missing</td>
<td>Move <code>COPY . .</code> after dependency install; add <code>.dockerignore</code></td>
</tr>
<tr>
<td>Cache never hits in CI</td>
<td>No cache backend configured</td>
<td>Add <code>--cache-from</code> / <code>--cache-to</code> with registry, gha, or s3 backend</td>
</tr>
<tr>
<td>Cache hits locally but not in CI</td>
<td>Different Docker versions or BuildKit not enabled</td>
<td>Set <code>DOCKER_BUILDKIT=1</code> and match Docker versions</td>
</tr>
<tr>
<td>Dependency layer always rebuilds</td>
<td>Source files copied before dependency install</td>
<td>Use the dependency-first pattern</td>
</tr>
<tr>
<td>Image size keeps growing</td>
<td>Build artifacts leaking into final image</td>
<td>Use multi-stage builds; only copy runtime artifacts</td>
</tr>
<tr>
<td>Registry cache is very slow</td>
<td><code>mode=max</code> caching too many layers</td>
<td>Try <code>mode=min</code> or switch to gha/s3 for faster backends</td>
</tr>
</tbody></table>
<h2 id="heading-quick-reference-checklist">Quick-Reference Checklist</h2>
<p>Print this out and tape it next to your monitor:</p>
<ul>
<li><p>[ ] Enable BuildKit: set <code>DOCKER_BUILDKIT=1</code> or use <code>docker buildx</code></p>
</li>
<li><p>[ ] Add a comprehensive <code>.dockerignore</code> file</p>
</li>
<li><p>[ ] Use the dependency-first pattern: copy manifests, install, then copy source</p>
</li>
<li><p>[ ] Order layers from least-changed to most-changed</p>
</li>
<li><p>[ ] Combine <code>RUN</code> commands that belong together (<code>apt-get update &amp;&amp; install</code>)</p>
</li>
<li><p>[ ] Use multi-stage builds to separate build and runtime</p>
</li>
<li><p>[ ] Add <code>RUN --mount=type=cache</code> for package manager caches</p>
</li>
<li><p>[ ] Move volatile <code>ARG</code>s (git hash, build number) to the very last layers</p>
</li>
<li><p>[ ] Configure a CI/CD cache backend (registry, gha, or s3)</p>
</li>
<li><p>[ ] Set up cache warming for feature branches from the main branch</p>
</li>
<li><p>[ ] Use <code>COPY</code> instead of <code>ADD</code> unless you need archive extraction</p>
</li>
<li><p>[ ] Benchmark all four scenarios: cold, warm, code change, dependency change</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I used to think slow Docker builds were just something you had to live with. After going through this process on a few projects, I realized the fix is pretty mechanical once you understand that one core principle: cache is sequential, and order matters.</p>
<p>Start with the dependency-first pattern and a <code>.dockerignore</code>. Those two changes alone will probably cut your build times in half. Then add multi-stage builds, mount caches, and CI/CD cache backends as you need them.</p>
<p>The teams I've worked with typically see 70-85% reductions in CI/CD pipeline times after spending a few hours on these changes. That's time you get back on every single commit, every single day.</p>
<p>If you found this helpful, consider sharing it with your team. There's a good chance whoever wrote your Dockerfile last didn't know about half of these tricks. No shade to them, I didn't either until I went looking.</p>
<p>Happy building.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Understanding Escape Analysis in Go – Explained with Example Code ]]>
                </title>
                <description>
                    <![CDATA[ In most languages, the stack and heap are two ways a program stores data in memory, managed by the language runtime. Each is optimized for different use cases, such as fast access or flexible lifetimes. Go follows the same model, but you usually don’... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/understanding-escape-analysis-in-go/</link>
                <guid isPermaLink="false">698e1c7090ca92017618cb24</guid>
                
                    <category>
                        <![CDATA[ Go Language ]]>
                    </category>
                
                    <category>
                        <![CDATA[ golang ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ heap ]]>
                    </category>
                
                    <category>
                        <![CDATA[ escape analysis ]]>
                    </category>
                
                    <category>
                        <![CDATA[ memory-management ]]>
                    </category>
                
                    <category>
                        <![CDATA[ memory ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eti Ijeoma ]]>
                </dc:creator>
                <pubDate>Thu, 12 Feb 2026 18:31:12 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770921059389/c45b42cb-8cff-4de5-b3d1-7c7adad402c5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In most languages, the stack and heap are two ways a program stores data in memory, managed by the language runtime. Each is optimized for different use cases, such as fast access or flexible lifetimes.</p>
<p>Go follows the same model, but you usually don’t decide between the stack and the heap directly. Instead, the Go compiler decides where values live. If the compiler can prove a value is only needed within the current function call, it can keep it on the stack. If it cannot prove that, the value “escapes” and is placed on the heap. This technique is called <strong>escape analysis</strong>.</p>
<p>This matters because heap allocations increase garbage collector work. In code that runs often, that extra work can show up as more CPU spent in GC, more allocations, and less predictable performance.</p>
<p>In this article, I’ll explain what escape analysis is, the common patterns that trigger heap allocation, and how to confirm and reduce avoidable allocations.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-do-you-really-need-to-care-about-escape-analysis">Do You Really Need to Care About Escape Analysis?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-memory-layout-and-lifecycle">Memory Layout and Lifecycle</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sharing-down-and-sharing-up">Sharing Down and Sharing Up</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-escape-analysis-in-practice">Escape Analysis in Practice</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-escape-analysis-to-guide-performance">How to Use Escape Analysis to Guide Performance</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-further-reading">Further Reading</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<ul>
<li><p>Familiarity with Go fundamentals (functions, variables, structs, slices, maps)</p>
</li>
<li><p>Basic understanding of pointers in Go (<code>&amp;</code> and <code>*</code>)</p>
</li>
<li><p>A general idea of how goroutines work</p>
</li>
</ul>
<h2 id="heading-do-you-really-need-to-care-about-escape-analysis">Do You Really Need to Care About Escape Analysis?</h2>
<p>Before we go deeper, I want to call this out clearly. For the correctness of your program, it doesn’t matter whether a variable lives on the stack or on the heap, or whether you know that detail. The Go compiler is smart enough to place values where they need to be so that your program behaves correctly.</p>
<p>Most of the time, you don’t need to think about this at all. It only starts to matter when performance becomes a problem. If your program is already fast enough, you’re done, and there’s no point trying to squeeze out extra speed.</p>
<p>You should only start caring about stack vs heap when you have benchmarks that show your program is too slow, and those same benchmarks point to heavy heap allocation and garbage collection as part of the problem.</p>
<h2 id="heading-memory-layout-and-lifecycle"><strong>Memory Layout and Lifecycle</strong></h2>
<p>To get a better understanding of what escape analysis is, you first need a simple picture of how Go lays out memory while your program runs. At this level, it comes down to the stack each goroutine uses, how stack frames are carved out of that stack, and when values move to the heap where the garbage collector can see them.</p>
<h3 id="heading-goroutine-stacks-and-stack-frames">Goroutine Stacks and Stack Frames</h3>
<p>When a Go program starts, the runtime creates the <code>main</code> goroutine, and every <code>go</code> statement creates a new goroutine, each with its own stack.</p>
<p>There’s not a single global stack for the whole process. As of writing this article, with Go v1.25.7, each goroutine gets an initial contiguous block of 2,048 bytes of memory, which acts as its stack. The stack is where Go stores data that belongs to function calls. When a goroutine calls a function, Go reserves a chunk of that goroutine’s stack for the function’s local data. That chunk is called a <strong>stack frame</strong>.</p>
<p>It holds the function’s local variables and the call state needed to return and continue execution. If that function calls another function, a new frame is added on top. When the inner function returns, its frame becomes invalid, and the goroutine continues in the caller’s frame.</p>
<p>A stack frame only lives for as long as the function is active. Once the function returns, anything stored in its frame is considered invalid, even if the raw bytes are still in memory and will be reused later. Code must not rely on those values after the return</p>
<p>Go stacks can grow. A goroutine starts with a small stack and the runtime grows it when needed, but the lifetime rule stays the same. A value is safe in a stack frame only if nothing can still reference it after the function returns. If it might be referenced later, it can’t stay in that frame and must be placed somewhere safer.</p>
<h3 id="heading-pointers-and-lifetime">Pointers and Lifetime</h3>
<p>In Go, taking an address like <code>p := &amp;x</code> means you now have a pointer in one stack frame that refers to a value which may have been created in another frame. When you pass that pointer into a function, Go still passes by value. The callee gets its own pointer variable on its own stack frame, but the address inside still points to the same underlying value. So pointers are how you share access to one value across several frames without copying the value itself.</p>
<p>Lifetime becomes important when a pointer can outlive the frame where the pointed value was created. As long as both the pointer and the value live inside frames that are still active in the current call stack, everything is safe.</p>
<p>Once a pointer might still exist after the original frame has returned, the value can no longer stay in that frame, because that frame will become invalid. At that point, the value has to be placed in a safer location so that no pointer ever points into dead stack memory.</p>
<h2 id="heading-sharing-down-and-sharing-up">Sharing Down and Sharing Up</h2>
<p>Now that you have a picture of stacks, frames, and pointers, we can look at two common ways pointers move through your code. I’ll call them sharing down and sharing up. The names aren’t special Go terms. They’re just a simple way to describe how a pointer moves along the call stack.</p>
<h3 id="heading-sharing-down">Sharing Down</h3>
<p>Sharing down means a function passes a pointer or reference to functions it calls. The pointer moves deeper into the call stack, but the value it points to still belongs to a frame that is active.</p>
<p>Example code:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    n := <span class="hljs-number">10</span>
    multiply(&amp;n) 
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">multiply</span><span class="hljs-params">(v *<span class="hljs-keyword">int</span>)</span></span> {
   *v = *v * <span class="hljs-number">2</span>
}
</code></pre>
<p>In <code>main</code>, you take the address of <code>n</code> and pass it into <code>multiply</code>. While <code>multiply</code> runs, both the <code>main</code> frame and the <code>multiply</code> frames are active. The pointer in <code>multiply</code> points to a value that still lives in an active frame, so this situation is safe from a lifetime point of view.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770153560595/0681b535-3668-406d-acaf-6e27679d52e1.png" alt="Diagram showing two stack frames on one goroutine, with the upper frame pointing to a value in the lower frame to illustrate sharing down on the stack" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>In the diagram below, after the <code>multiply</code> function runs and returns, the <code>multiply</code> frame becomes invalid, and we don’t need to do anything because the stack pointer is simply popped back to the previous frame's address. This action automatically reclaims all the memory used by that function in one step, so the garbage collector is not involved in cleaning up stack memory</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770153730555/9093b49a-a8bc-497d-be06-77738a89a6c4.png" alt="Diagram showing two stack frames with a value in the upper frame updated through a pointer stored in the lower frame, again illustrating sharing down entirely on the stack" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-sharing-up">Sharing Up</h3>
<p>Sharing up means a function returns a pointer, or stores it somewhere that will still be around after the function returns. The pointer moves back up the call stack or into some longer-lived state while the frame that created the value is about to end, so that value can no longer be tied to that one frame.</p>
<p>The same idea shows up when you share a value with another goroutine, because Go doesn’t let one goroutine hold pointers into another goroutine’s stack, so shared data needs a lifetime that is not tied to a single stack.</p>
<h4 id="heading-heap-garbage-collection-and-lifetime">Heap, garbage collection, and lifetime</h4>
<p>Values that might outlive a single stack frame can’t stay in that frame. The compiler places them on the heap instead. The heap is a separate region of memory that isn’t tied to one function call. Any goroutine can hold pointers to heap values, and those values stay valid as long as something in the program can still reach them. You can think of the heap as storage for “<em>might live longer than this call</em>”.</p>
<p>The garbage collector is what keeps this safe. Periodically, the runtime starts from a set of roots (global variables, active stack frames, some internal state) and follows all the pointers it can see. Any heap value that is still reachable is kept. Any heap value that is no longer reachable is treated as garbage and its memory is reclaimed.</p>
<p>This means a pointer in <code>main</code> will never legally point into dead stack memory. Either the value stayed in an active frame, or it was placed on the heap where the GC can track its lifetime. The tradeoff is that more heap allocations and longer-lived objects require the GC to do more work.</p>
<p>Here’s an example:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-keyword">type</span> Car <span class="hljs-keyword">struct</span> {
    Brand <span class="hljs-keyword">string</span>
    Model <span class="hljs-keyword">string</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// main receives a pointer from a function it called and this is sharing up</span>
    carPtr := makeCar(<span class="hljs-string">"Volkswagen"</span>, <span class="hljs-string">"Golf"</span>) 

    fmt.Printf(<span class="hljs-string">"I received a car: %s %s\n"</span>, carPtr.Brand, carPtr.Model)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">makeCar</span><span class="hljs-params">(b, m <span class="hljs-keyword">string</span>)</span> *<span class="hljs-title">Car</span></span> {
    myCar := Car{
        Brand: b,
        Model: m,
    }
    <span class="hljs-keyword">return</span> &amp;myCar
}
</code></pre>
<p>In the above code:</p>
<ol>
<li><p>In <code>makeCar</code> (the callee frame), Go creates a local variable <code>myCar</code>. Because you return <code>&amp;myCar</code>, the compiler allocates the <code>Car</code> value on the heap, and let’s <code>myCar</code> hold the heap address <code>0xc00029fa0</code>.</p>
</li>
<li><p>When <code>makeCar</code> returns, that address is copied into <code>carPtr</code> in <code>main</code> (the top frame). <code>carPtr</code> is just another stack variable, but its value is still <code>0xc00029fa0</code>, so now <code>main</code> also points to the same heap <code>Car</code>.</p>
</li>
<li><p>On the right, the heap bubble shows the actual <code>Car</code> value at <code>0xc00029fa0</code>. Both <code>car</code> (while <code>makeCar</code> is running) and <code>carPtr</code> (after it returns) reach that same value through their pointers.</p>
</li>
<li><p>Once <code>makeCar</code> is done, its frame drops into the “invalid memory” region, but the <code>Car</code> stays alive on the heap because <code>main</code> still holds <code>carPtr</code>. That’s the escape: the value stops being tied to the callee frame and gets heap lifetime instead.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770255008545/5ef80c9b-3203-4ca3-a26e-f1e2462912cf.png" alt="Diagram showing a caller and callee stack frame both holding a pointer to the same value in heap memory, illustrating a value being shared up and escaping the stack" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-escape-analysis-in-practice">Escape Analysis in Practice</h2>
<p>Escape analysis is how the Go compiler decides whether a value lives on the stack or on the heap. It’s not only about returning pointers – it follows how addresses move through your code. If a value might outlive the current function, the compiler can’t keep it in that stack frame and moves it to the heap. Since only the compiler sees the full picture, the useful thing is to ask it to show these decisions and then link them back to your code.</p>
<p>To do that, we can pass compiler flags using <code>-gcflags</code> when running <code>go build</code> or <code>go run</code>. If you want to see the available options, you can check <code>go tool compile -h</code>. In that list, <code>-m</code> prints the compiler’s optimisation decisions, including escape analysis output. If you want more details, you can use <code>-m=2</code> or <code>-m=3</code> for a more verbose output. The <code>-l</code> flag disables inlining, so the report is easier to read because the compiler is not merging small functions into their callers.</p>
<p>So, the command will look like this:</p>
<pre><code class="lang-bash">go run -gcflags=<span class="hljs-string">'all=-m -l'</span> .
</code></pre>
<p>Or for a build:</p>
<pre><code class="lang-bash">go build -gcflags=<span class="hljs-string">'all=-m -l'</span> .
</code></pre>
<h2 id="heading-how-to-use-escape-analysis-to-guide-performance">How to Use Escape Analysis to Guide Performance</h2>
<p>You can think of escape analysis as the thing that turns your code choices into GC work. When a value escapes, it gets heap lifetime, and the garbage collector has to visit it. In hot paths, lots of small escaping values show up as extra GC time and jitter in latency. When a value stays in a stack frame, it becomes invalid and dies with the frame and the GC does not care about it.</p>
<p>Here are five simple practices that help performance without making</p>
<ol>
<li><p><strong>Prefer values for small data:</strong> If the function doesn’t need to mutate the caller’s data, use value types for small structs and basic types when passing arguments and returning results. It’s cheap to copy an <code>int</code> or a small struct, and it often keeps lifetimes local to a single call.</p>
</li>
<li><p><strong>Use pointers when sharing or mutation is part of the design:</strong> opt for pointers when you genuinely need shared mutable state or want to avoid copying large structs.</p>
</li>
<li><p><strong>Avoid creating long-lived references by accident</strong>: Be careful when returning pointers to locals, capturing variables in closures, or storing addresses in long-lived structs, maps, or interfaces. These patterns are the ones most likely to push values out of a stack frame.</p>
</li>
<li><p><strong>Pass in reusable buffers on hot paths</strong>: On code paths that run very often, the problem is usually not one big allocation, but many small ones happening in a loop. A common cause is functions that always create a new buffer inside, even when the caller could have passed one in.</p>
<p> A simple way to cut those extra allocations is to let the caller own the buffer. The caller allocates a <code>[]byte</code> once, then passes it into the function each time. The function only fills the buffer instead of creating a new one.</p>
<p> Here’s an example of how a bad function allocates a new buffer every call:</p>
<pre><code class="lang-go"> <span class="hljs-keyword">package</span> main

 <span class="hljs-comment">// Bad: helper allocates every call.</span>
 <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fillBad</span><span class="hljs-params">()</span> []<span class="hljs-title">byte</span></span> {
     buf := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">byte</span>, <span class="hljs-number">4096</span>)
     <span class="hljs-comment">// pretend we read into it</span>
     buf[<span class="hljs-number">0</span>] = <span class="hljs-number">1</span>
     <span class="hljs-keyword">return</span> buf
 }

 <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">hotPathBad</span><span class="hljs-params">()</span></span> {
     <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">1</span>_000_000; i++ {
         b := fillBad() <span class="hljs-comment">// allocates 1,000,000 times</span>
         _ = b
     }
 }

 <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
     hotPathBad()
 }
</code></pre>
<p> When we run escape analysis with this:</p>
<pre><code class="lang-bash"> go run -gcflags=<span class="hljs-string">'-m -l'</span> .
</code></pre>
<p> We see the following:</p>
<pre><code class="lang-plaintext"> ./main.go:5:13: make([]byte, 4096) escapes to heap
</code></pre>
<p> If we were only allocating a few times, we could choose not to worry – but the real problem is how this looks inside the loop. <code>hotPathBad</code> calls <code>fillBad</code> on every iteration, so each call allocates a new 4 KB slice on the heap. If this loop runs many times, you end up creating a lot of short-lived heap objects. The garbage collector then has to find and clean up all those buffers, which adds extra work that you could have avoided by reusing a single buffer.  </p>
<p> Here’s an example of a better version where the caller allocates once and reuses:</p>
<pre><code class="lang-go"> <span class="hljs-keyword">package</span> main

 <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fill</span><span class="hljs-params">(buf []<span class="hljs-keyword">byte</span>)</span> <span class="hljs-title">int</span></span> {
     <span class="hljs-comment">// pretend we read into it</span>
     buf[<span class="hljs-number">0</span>] = <span class="hljs-number">1</span>
     <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
 }

 <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">hotPath</span><span class="hljs-params">()</span></span> {
     buf := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">byte</span>, <span class="hljs-number">4096</span>) 

     <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">1</span>_000_000; i++ {
         n := fill(buf) 
         _ = buf[:n]
     }
 }

 <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
     hotPath()
 }
</code></pre>
<p> In this version, <code>hotPath</code> controls the buffer. It allocates <code>buf</code> once, then passes it into <code>fill</code> on every loop. You still read the same data, but you avoid creating a new slice on each call. That reduces avoidable allocations in the hot path.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In Go, where a value ends up is not decided by how you create it. It’s decided by how long that value must remain valid and how it is referenced as your code runs.</p>
<p>The practical takeaway is not to avoid pointers. It’s to be deliberate about lifetime. Value semantics can keep lifetimes tight and reduce GC work, while pointers can be the right choice when you need shared state or in-place updates. The balance is to write the clear version first, then look at your benchmarks and profiles to see if anything actually really needs to change.</p>
<h2 id="heading-further-reading">Further Reading</h2>
<p>Language Mechanics On Stacks And Pointers - William Kennedy</p>
<p><a target="_blank" href="https://go.dev/doc/gc-guide">Go Compiler: Escape Analysis Flaws</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Test and Improve AI Applications with an Evaluation Flywheel ]]>
                </title>
                <description>
                    <![CDATA[ In traditional programming, developers rely on unit tests to catch mistakes in applications. But when building AI products, that safety net doesn't exist. Responses can shift with model updates, data changes, and subtle fluctuations in prompts or ret... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-test-and-improve-ai-applications-with-an-evaluation-flywheel/</link>
                <guid isPermaLink="false">69491adc842069e2b48bbae7</guid>
                
                    <category>
                        <![CDATA[ ai agents ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Yemi Ojedapo ]]>
                </dc:creator>
                <pubDate>Mon, 22 Dec 2025 10:18:04 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766082262126/bc54e004-7acc-49fc-b228-24524f250427.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In traditional programming, developers rely on unit tests to catch mistakes in applications. But when building AI products, that safety net doesn't exist. Responses can shift with model updates, data changes, and subtle fluctuations in prompts or retrieval results. The usual testing methods like unit tests with Pytest or Jest, integration tests, CI pipelines, fail to catch accuracy drops, hallucinations, or regressions, and these silent failures can become real production risks.</p>
<p>In this article, you’ll learn why traditional testing methods fall short for AI systems and how an evaluation flywheel can be used as a practical approach to testing and improving AI applications. The sections below break the evaluation flywheel down step by step, from identifying the problem to implementing a repeatable evaluation loop.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-does-traditional-testing-fail-for-ai-applications">Why Does Traditional Testing Fail for AI applications?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-the-evaluation-flywheel">What is the Evaluation Flywheel?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-drawing-parallels-to-familiar-practices">Drawing Parallels to Familiar Practices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-silent-failures-matter-a-real-world-example">Why Silent Failures Matter: A Real-World Example</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-an-evaluation-flywheel">How to Create an Evaluation Flywheel</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-tools-and-frameworks-you-can-use-for-evaluation">Tools and Frameworks you can use for evaluation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-a-complete-evaluation-loop-looks-like-in-practice">What a Complete Evaluation Loop Looks Like in Practice</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-key-takeaways">Key Takeaways</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-why-does-traditional-testing-fail-for-ai-applications">Why Does Traditional Testing Fail for AI applications?</h2>
<p>In standard programming, tests assume deterministic behavior. This means the same input is expected to always produce the same output. For example:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">authenticate_user_age</span>(<span class="hljs-params">age: int</span>) -&gt; str:</span>
    limit = <span class="hljs-number">18</span>

    <span class="hljs-keyword">if</span> age &gt;= limit:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Access granted"</span>
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"User doesn't meet the age limit"</span>

<span class="hljs-comment"># Test </span>
<span class="hljs-keyword">assert</span> authenticate_user_age(<span class="hljs-number">20</span>) == <span class="hljs-string">"Access granted"</span>
<span class="hljs-keyword">assert</span> authenticate_user_age(<span class="hljs-number">16</span>) == <span class="hljs-string">"User doesn't meet the age limit"</span>
</code></pre>
<p>The response from this function is always predictable. You can write tests once and trust they'll catch errors forever.</p>
<p>However, AI models don’t behave the same way every time, they generate output based on probabilities. A query like “best programming practices” may produce strong guidance one day, and outdated or incomplete advice the next. This shift can happen because of changes in the underlying model, updates to retrieval components, or gradual data drift. Without a structured evaluation process in place, these inconsistencies slip into production unnoticed and can quietly weaken the system’s performance.</p>
<h2 id="heading-what-is-the-evaluation-flywheel">What is the Evaluation Flywheel?</h2>
<p>The evaluation flywheel is a continuous improvement system where test cases representing real user behavior are passed through multiple evaluation steps to assess the output of AI models. The results don't just tell you whether the system passed or failed, they feed directly into the next cycle of improvement.</p>
<pre><code class="lang-plaintext">┌─────────────┐
│   Collect   │
│ Test Cases  │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│     Run     │
│ Evaluations │
└──────┬──────┘
       │
       ▼
┌─────────────┐      ┌─────────────┐
│  Identify   │─────▶│   Improve   │
│  Failures   │      │   System    │
└─────────────┘      └──────┬──────┘
                            │
                            ▼
                       ┌─────────────┐
                       │   Repeat    │
                       └─────────────┘
</code></pre>
<p>Here's how it works in practice:</p>
<ul>
<li><p><strong>Collect test cases</strong> — Gather examples from real user interactions or create synthetic scenarios. These should reflect the kind of tasks and input your system needs to handle.</p>
</li>
<li><p><strong>Run evaluations</strong> — Pass each test case through a series of checks. The check can either be programmatic (automated metrics like relevance scores or hallucination detectors) or require manual review (like verifying legal advice accuracy or brand voice consistency).</p>
</li>
<li><p><strong>Identify failures</strong> — Detect where the model goes wrong, this can include hallucinations, irrelevant responses, or mistakes on corner-cases.</p>
</li>
<li><p><strong>Improve the system</strong> — Based on those failures, refine prompts, improve training or retrieval data, or adjust architectural components.</p>
</li>
<li><p><strong>Repeat the cycle</strong> — Re-run the updated system on the existing and newly collected cases. Over time, this grows and strengthens your evaluation suite and boosts system reliability.</p>
</li>
</ul>
<h2 id="heading-drawing-parallels-to-familiar-practices">Drawing Parallels to Familiar Practices</h2>
<p>If you've written software before, the evaluation flywheel will feel familiar. It mirrors patterns that are already used in engineering. For instance,</p>
<p><strong>Unit tests → Evaluation datasets</strong><br>Unit tests confirm a function returns the right output. Evaluation datasets play the same role for AI: they're ground-truth queries and answers that guard against regressions.</p>
<p><strong>Test-driven development (TDD) → Evaluation-driven development (EDD)</strong><br>In TDD, you write tests before code. In EDD, you write evaluation cases before shipping prompts or updating models. This replaces assumptions with verifiable results.</p>
<p><strong>CI/CD pipelines → Continuous evaluation pipelines</strong><br>CI/CD runs checks automatically on every code change. Continuous evaluation does the same for models: it runs automated quality checks every time you tweak a prompt, retrain, or swap out a component.</p>
<p>The key difference is subtle but important. Traditional software tests check whether a function returns the right value or type. AI evaluation tests check whether the system produces the right <em>meaning</em>. That's harder to measure, but the principle is the same: build a safety net that grows stronger with every cycle.</p>
<h2 id="heading-why-silent-failures-matter-a-real-world-example">Why Silent Failures Matter: A Real-World Example</h2>
<p>AI systems often behave differently in production than they do in development. A model that seems solid in testing can drift, hallucinate, or silently fail when facing real-world input.</p>
<p><strong>Case in point</strong>: A fraud detection model passed all monitoring metrics yet missed a spike in fraud. An ML engineer shared how their production monitoring dashboards tracked latency, throughput, and error rates, everything showed green. But fraudulent transactions were slipping through at twice the normal rate. Nobody noticed because existing observability tools focused on pipeline health, not prediction quality.</p>
<p>This silent failure cost the company significant losses. The system seemed fine by traditional metrics. It measured system performance—latency, throughput, uptime—but ignored what mattered most: prediction accuracy. As fraudsters adapted their tactics, the model drifted, and without proper evaluation loops, the degradation went undetected for weeks.</p>
<p>Source: <a target="_blank" href="https://insightfinder.com/blog/model-drift-ai-observability/">InsightFinder</a>.</p>
<h3 id="heading-why-this-example-matters">Why This Example Matters</h3>
<ul>
<li><p><strong>Silent failures aren't always bugs</strong> — They often stem from models failing to adapt to shifting patterns in the real world.</p>
</li>
<li><p><strong>Static evaluation isn't enough</strong> — You need continuous, real-world feedback loops to detect when assumptions no longer hold.</p>
</li>
<li><p><strong>Data drift has business impact</strong> — Model degradation isn't just technical, it translates directly into revenue loss, security breaches, or damaged user trust.</p>
</li>
</ul>
<h2 id="heading-how-to-create-an-evaluation-flywheel">How to Create an Evaluation Flywheel</h2>
<p>To show how to build a flywheel and how it works, let's create one for a customer support chatbot that answers questions about a SaaS product.</p>
<h3 id="heading-step-1-build-your-ai-system"><strong>Step 1: Build Your AI System</strong></h3>
<p>Create your initial product: prompts, retrieval logic, and integrations. For our chatbot:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">answer_support_question</span>(<span class="hljs-params">question: str</span>) -&gt; str:</span>
    <span class="hljs-comment"># Retrieve relevant docs from knowledge base</span>
    context = retrieve_docs(question, top_k=<span class="hljs-number">5</span>)

    <span class="hljs-comment"># Generate answer using LLM</span>
    prompt = <span class="hljs-string">f"""You are a helpful customer support agent.

Context: <span class="hljs-subst">{context}</span>

Question: <span class="hljs-subst">{question}</span>

Provide a clear, accurate answer based on the context."""</span>

    response = llm.generate(prompt)
    <span class="hljs-keyword">return</span> response
</code></pre>
<p><strong>How this works:</strong> This function defines the core chat logic, it takes a customer’s question and returns an AI-generated answer. First, it searches your knowledge base to find the five most relevant documents using <code>retrieve_docs()</code>. These documents provide context about your product or policies. Next, it constructs a prompt that includes this context and the user's question, then sends it to a language model. The LLM reads the context and generates a relevant answer, which the function returns.</p>
<h3 id="heading-step-2-identify-test-cases">Step 2: Identify Test Cases</h3>
<p>Build an evaluation set that reflects real user behavior. The more representative your test cases are, including common cases, edge cases, and ambiguous inputs, the better your model can catch failures before they reach production.</p>
<p><strong>Sources for test cases:</strong></p>
<ul>
<li><p>Previous customer support tickets</p>
</li>
<li><p>Common FAQ topics</p>
</li>
<li><p>Edge cases discovered in beta testing</p>
</li>
<li><p>Synthetic scenarios (hypothetical but realistic queries)</p>
</li>
</ul>
<p>Example test cases:</p>
<pre><code class="lang-python">test_cases = [
    {
        <span class="hljs-string">"question"</span>: <span class="hljs-string">"How do I reset my password?"</span>,
        <span class="hljs-string">"expected_elements"</span>: [<span class="hljs-string">"settings page"</span>, <span class="hljs-string">"reset link"</span>, <span class="hljs-string">"email"</span>],
        <span class="hljs-string">"category"</span>: <span class="hljs-string">"account_management"</span>
    },
    {
        <span class="hljs-string">"question"</span>: <span class="hljs-string">"What's your refund policy?"</span>,
        <span class="hljs-string">"expected_elements"</span>: [<span class="hljs-string">"30 days"</span>, <span class="hljs-string">"full refund"</span>, <span class="hljs-string">"contact support"</span>],
        <span class="hljs-string">"category"</span>: <span class="hljs-string">"billing"</span>
    },
    {
        <span class="hljs-string">"question"</span>: <span class="hljs-string">"Can I export my data to CSV?"</span>,
        <span class="hljs-string">"expected_elements"</span>: [<span class="hljs-string">"yes"</span>, <span class="hljs-string">"export button"</span>, <span class="hljs-string">"dashboard"</span>],
        <span class="hljs-string">"category"</span>: <span class="hljs-string">"features"</span>
    },
    {
        <span class="hljs-string">"question"</span>: <span class="hljs-string">"Does your API support webhooks?"</span>,
        <span class="hljs-string">"expected_elements"</span>: [<span class="hljs-string">"yes"</span>, <span class="hljs-string">"webhook endpoints"</span>, <span class="hljs-string">"documentation"</span>],
        <span class="hljs-string">"category"</span>: <span class="hljs-string">"technical"</span>
    }
]
</code></pre>
<p><strong>How this works:</strong> Here, we define a set of representative test cases to evaluate the AI system. Each test case includes the user’s question, a list of key elements expected in the answer, and a category for organization. These cases help ensure the chatbot is tested against real-world scenarios, edge cases, and important information that should appear in responses.</p>
<h3 id="heading-step-3-evaluate-outputs">Step 3: Evaluate Outputs</h3>
<p>Define evaluation criteria based on what matters for your use case: accuracy, faithfulness, safety, relevance, tone. Then measure the output against these criteria.</p>
<p>Evaluation happens in two main ways:</p>
<h4 id="heading-automated-evaluation">Automated Evaluation</h4>
<p>Use programmatic metrics and LLM-as-judge patterns:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">evaluate_response</span>(<span class="hljs-params">question: str, response: str, expected_elements: list</span>) -&gt; dict:</span>
    scores = {}

    <span class="hljs-comment"># 1. Faithfulness: Does response contain expected elements?</span>
    scores[<span class="hljs-string">'contains_key_info'</span>] = all(
        elem.lower() <span class="hljs-keyword">in</span> response.lower() 
        <span class="hljs-keyword">for</span> elem <span class="hljs-keyword">in</span> expected_elements
    )

    <span class="hljs-comment"># 2. Relevance: Semantic similarity to question</span>
    scores[<span class="hljs-string">'relevance'</span>] = calculate_semantic_similarity(question, response)

    <span class="hljs-comment"># 3. Safety: Check for problematic content</span>
    scores[<span class="hljs-string">'is_safe'</span>] = <span class="hljs-keyword">not</span> contains_harmful_content(response)

    <span class="hljs-comment"># 4. Tone: Use LLM-as-judge</span>
    judge_prompt = <span class="hljs-string">f"""Rate the helpfulness of this support response on a scale of 1-5.

Question: <span class="hljs-subst">{question}</span>
Response: <span class="hljs-subst">{response}</span>

Score (1-5):"""</span>

    scores[<span class="hljs-string">'helpfulness'</span>] = int(llm.generate(judge_prompt))

    <span class="hljs-keyword">return</span> scores

<span class="hljs-comment"># Run evaluation</span>
<span class="hljs-keyword">for</span> test_case <span class="hljs-keyword">in</span> test_cases:
    response = answer_support_question(test_case[<span class="hljs-string">'question'</span>])
    scores = evaluate_response(
        test_case[<span class="hljs-string">'question'</span>],
        response,
        test_case[<span class="hljs-string">'expected_elements'</span>]
    )
    test_case[<span class="hljs-string">'scores'</span>] = scores
    test_case[<span class="hljs-string">'response'</span>] = response
</code></pre>
<p><strong>How this works:</strong> The <code>evaluate_response()</code> function applies four different checks to each AI response:</p>
<ul>
<li><p>First, it verifies faithfulness by checking if all expected elements appear in the response using simple string matching.</p>
</li>
<li><p>Second, it calculates semantic similarity, a measure of how closely the responses meaning match the intent of the questions, using embeddings.</p>
</li>
<li><p>Third, it runs a safety check to flag any problematic content.</p>
</li>
<li><p>Fourth, it uses an LLM as a judge by asking a more powerful model (like GPT-4) to rate the helpfulness of the response on a 1-5 scale.</p>
</li>
</ul>
<p>The loop then runs the evaluation for every test case. It generates a response for each question, evaluates it using the <code>evaluate_response</code> function, and then stores both the scores and the response back in the test case. This creates a complete dataset of test results for analysis and further improvements.</p>
<p>Common Automated Metrics:</p>
<ul>
<li><p><strong>Semantic similarity (0.0–1.0):</strong> This is measured by converting the question and response into vector embeddings and calculating cosine similarity. The score shows how closely the response matches the intent of the question, even if the wording differs.</p>
</li>
<li><p><strong>ROUGE / BLEU scores:</strong> The model’s output is compared to reference answers by checking n-gram overlap. These metrics help spot regressions, though scores can be modest for open-ended answers.</p>
</li>
<li><p><strong>LLM-as-judge:</strong> A stronger model (like GPT-4 or Claude) can rate the response on a fixed scale, such as 1–5. These ratings give a sense of quality and are useful for tracking improvements or drops over time.</p>
</li>
<li><p><strong>Retrieval metrics (Precision@k, Recall@k):</strong> For retrieval-based systems, these metrics calculate how many relevant documents appear in the top-k results. Precision shows accuracy of the retrieved set, and recall indicates completeness.</p>
</li>
<li><p><strong>Custom validators:</strong> Simple rule-based checks, like regex patterns, keywords, or length limits, ensure responses meet hard requirements. These help catch issues automated metrics might miss.</p>
</li>
</ul>
<h4 id="heading-manual-evaluation">Manual Evaluation</h4>
<p>Automated metrics can't capture everything. Subjective qualities like tone, empathy, and brand voice require human judgment, as do small factual errors that slip past keyword checks and similarity scores.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Flag cases for human review</span>
needs_review = [
    case <span class="hljs-keyword">for</span> case <span class="hljs-keyword">in</span> test_cases 
    <span class="hljs-keyword">if</span> case[<span class="hljs-string">'scores'</span>][<span class="hljs-string">'helpfulness'</span>] &lt; <span class="hljs-number">3</span> 
    <span class="hljs-keyword">or</span> <span class="hljs-keyword">not</span> case[<span class="hljs-string">'scores'</span>][<span class="hljs-string">'contains_key_info'</span>]
]

<span class="hljs-comment"># SMEs review and annotate</span>
<span class="hljs-keyword">for</span> case <span class="hljs-keyword">in</span> needs_review:
    annotation = get_sme_feedback(case)
    case[<span class="hljs-string">'human_rating'</span>] = annotation[<span class="hljs-string">'rating'</span>]
    case[<span class="hljs-string">'improvement_notes'</span>] = annotation[<span class="hljs-string">'notes'</span>]
</code></pre>
<p>This code filters test cases to find responses that need human attention, those scoring below 3 for helpfulness or missing important information. Subject matter experts review these flagged cases and provide ratings with helpful feedback. Their input helps you spot patterns that automated metrics miss and shows you where to improve your prompts, retrieval setup, or system settings.</p>
<p><strong>When to use manual evaluation:</strong></p>
<ul>
<li><p>Assessing tone, empathy, or brand voice</p>
</li>
<li><p>Detecting subtle hallucinations automated checks miss</p>
</li>
<li><p>Validating edge cases with domain-specific nuance</p>
</li>
<li><p>Creating ground truth labels for training evaluation models</p>
</li>
</ul>
<h3 id="heading-step-4-learn-and-improve">Step 4: Learn and Improve</h3>
<p>Once you've identified failures, adjust the controllable parts of your AI system (the "configs"):</p>
<p><strong>Common configuration levers:</strong></p>
<ul>
<li><p><strong>Prompts</strong> — Add instructions, examples, constraints</p>
</li>
<li><p><strong>Retrieval</strong> — Change chunk size, top-k, reranking strategy</p>
</li>
<li><p><strong>Model</strong> — Switch models, adjust temperature, max tokens</p>
</li>
<li><p><strong>Context</strong> — Modify system instructions, add memory</p>
</li>
<li><p><strong>Post-processing</strong> — Add validation, formatting, safety filters</p>
</li>
</ul>
<p><strong>Example improvement cycle:</strong></p>
<pre><code class="lang-python"><span class="hljs-comment"># Problem discovered: Chatbot missing key details</span>
failing_case = {
    <span class="hljs-string">"question"</span>: <span class="hljs-string">"What's your refund policy?"</span>,
    <span class="hljs-string">"response"</span>: <span class="hljs-string">"We offer refunds in certain cases."</span>,
    <span class="hljs-string">"issue"</span>: <span class="hljs-string">"Too vague, missing 30-day window and process"</span>
}

<span class="hljs-comment"># Root cause: Retrieval returning wrong docs</span>
retrieved_docs = retrieve_docs(failing_case[<span class="hljs-string">'question'</span>], top_k=<span class="hljs-number">5</span>)
<span class="hljs-comment"># Docs about "payment processing" ranked higher than "refund policy"</span>

<span class="hljs-comment"># Solution 1: Improve retrieval with reranking</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">retrieve_docs_v2</span>(<span class="hljs-params">question: str, top_k: int</span>) -&gt; str:</span>
    <span class="hljs-comment"># Initial retrieval</span>
    candidates = vector_search(question, top_k=<span class="hljs-number">20</span>)

    <span class="hljs-comment"># Rerank by relevance</span>
    reranked = rerank_by_relevance(question, candidates)

    <span class="hljs-keyword">return</span> reranked[:top_k]

<span class="hljs-comment"># Solution 2: Update prompt to require specificity</span>
prompt_v2 = <span class="hljs-string">f"""You are a helpful customer support agent.

Context: <span class="hljs-subst">{context}</span>

Question: <span class="hljs-subst">{question}</span>

Provide a clear, accurate answer based on the context. Include specific details like:
- Time windows (e.g., "within 30 days")
- Step-by-step processes
- Relevant links or contact methods

Answer:"""</span>

<span class="hljs-comment"># Re-evaluate</span>
new_response = answer_support_question_v2(failing_case[<span class="hljs-string">'question'</span>])
new_scores = evaluate_response(
    failing_case[<span class="hljs-string">'question'</span>],
    new_response,
    [<span class="hljs-string">"30 days"</span>, <span class="hljs-string">"full refund"</span>, <span class="hljs-string">"contact support"</span>]
)

<span class="hljs-comment"># Verify improvement</span>
<span class="hljs-keyword">assert</span> new_scores[<span class="hljs-string">'contains_key_info'</span>] == <span class="hljs-literal">True</span>
<span class="hljs-keyword">assert</span> new_scores[<span class="hljs-string">'helpfulness'</span>] &gt;= <span class="hljs-number">4</span>
</code></pre>
<p><strong>How this works:</strong> In this example, the chatbot's refund answer was too vague. After checking what went wrong, the problem was that the system retrieved docs about payment processing instead of the refund policy.</p>
<p>To resolve this, two changes can be made. First, retrieval is improved by grabbing twenty documents, then picking the best five. Second, the prompt is updated to ask for specific details like dates and steps.</p>
<p>After making these changes, the test runs again to confirm it works: the response now has all the key info and scores at least 4 out of 5. This process turns problems into fixes you can measure.</p>
<h3 id="heading-step-5-automate-and-repeat">Step 5: Automate and Repeat</h3>
<p>Integrate evaluation into your development workflow using CI/CD:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># .github/workflows/eval.yml</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">Continuous</span> <span class="hljs-string">Evaluation</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">pull_request:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [<span class="hljs-string">main</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">evaluate:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">evaluation</span> <span class="hljs-string">suite</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">python</span> <span class="hljs-string">run_evals.py</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Check</span> <span class="hljs-string">pass</span> <span class="hljs-string">rate</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          PASS_RATE=$(python calculate_pass_rate.py)
          if (( $(echo "$PASS_RATE &lt; 0.85" | bc -l) )); then
            echo "Pass rate $PASS_RATE below threshold"
            exit 1
          fi
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">results</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">eval-results</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">results/</span>
</code></pre>
<p><strong>Explanation:</strong> This GitHub Actions workflow automates your evaluation process so it runs automatically on every code change. The workflow triggers whenever someone opens a pull request or pushes code to the main branch. It checks out your code, runs your full evaluation suite using <code>run_</code><a target="_blank" href="http://evals.py"><code>evals.py</code></a>, then calculates what percentage of test cases passed. If the pass rate drops below 85%, the workflow fails and blocks the code from being merged, preventing quality regressions from reaching production.</p>
<p><strong>Key practices for automation:</strong></p>
<ul>
<li><p><strong>Version your test cases</strong> — Track them in Git alongside code</p>
</li>
<li><p><strong>Set quality gates</strong> — Block deployments if pass rate drops below threshold</p>
</li>
<li><p><strong>Monitor trends</strong> — Track metrics over time to catch gradual drift</p>
</li>
<li><p><strong>Alert on regressions</strong> — Notify team when specific test cases start failing</p>
</li>
<li><p><strong>Sample production traffic</strong> — Continuously add real queries to eval dataset</p>
</li>
</ul>
<h2 id="heading-tools-and-frameworks-you-can-use-for-evaluation">Tools and Frameworks you can use for evaluation</h2>
<p>Several platforms can help implement continuous evaluation. The one you choose depends on your stack and needs:</p>
<p><strong>If you're building with LLMs:</strong> Try LangSmith or Braintrust first. Both handle prompt versioning, evaluation datasets, and tracing out of the box.</p>
<p><strong>If you're doing traditional ML:</strong> Weights &amp; Biases is the industry standard. If you're in the Microsoft ecosystem, PromptFlow integrates well with Azure.</p>
<p><strong>If you want full control:</strong> Build custom with pytest for test execution and MLflow for tracking results. More setup, but you own the entire pipeline</p>
<h2 id="heading-what-a-complete-evaluation-loop-looks-like-in-practice">What a Complete Evaluation Loop Looks Like in Practice</h2>
<p>This walkthrough shows how a support chatbot improves after running a single cycle of evaluations. Each stage shows how evaluation signals guide improvements and lock in quality for the next release.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Stage</td><td>Before</td><td>After</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Test Case</strong></td><td>"Can I use your API on the free plan?"</td><td>Same question</td></tr>
<tr>
<td><strong>Model Response</strong></td><td>"Yes, you can access our API."</td><td>"Yes, you can access our API on the free plan with a rate limit of 100 requests per day. For higher limits, upgrade to Pro or Enterprise."</td></tr>
<tr>
<td><strong>Evaluation Scores</strong></td><td>contains_key_info=False, helpfulness=2/5</td><td>contains_key_info=True, helpfulness=5/5</td></tr>
<tr>
<td><strong>Issue Identified</strong></td><td>Missing crucial detail: free plan rate limits</td><td>N/A (issue resolved)</td></tr>
<tr>
<td><strong>Analysis / Root Cause</strong></td><td>Retrieval returned general API docs; prompt didn’t emphasize limitations</td><td>N/A (analysis led to fix)</td></tr>
<tr>
<td><strong>Fixes Applied</strong></td><td>1. Improved retrieval to fetch plan comparison docs2. Updated prompt: "Always mention plan-specific restrictions"3. Added validation: Response must mention rate limits if asked</td><td>N/A (fix implemented)</td></tr>
<tr>
<td><strong>Outcome</strong></td><td>Test failed, regression not prevented</td><td>Test passes, regression prevented</td></tr>
<tr>
<td><strong>Next Cycle Actions</strong></td><td>N/A</td><td>1. Add this test case to permanent suite 2. Look for similar issues (other plan-related questions) 3. Monitor production queries for this pattern</td></tr>
</tbody>
</table>
</div><p><strong>Next cycle:</strong></p>
<ul>
<li><p>Add this test case to permanent suite</p>
</li>
<li><p>Look for similar issues (other plan-related questions)</p>
</li>
<li><p>Monitor if this pattern appears in production queries</p>
</li>
</ul>
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<ul>
<li><p><strong>AI systems need continuous evaluation, not one-time testing</strong> — Models drift, data changes, and silent failures accumulate without ongoing checks.</p>
</li>
<li><p><strong>Build evaluation into your workflow from day one</strong> — Don't wait until production failures force you to retrofit evaluation.</p>
</li>
<li><p><strong>Start simple, then scale</strong> — Begin with 10-20 test cases and basic metrics. Grow your suite as you encounter edge cases.</p>
</li>
<li><p><strong>Automate what you can, involve humans for what you can't</strong> — Use programmatic checks for speed, SME review for nuance.</p>
</li>
<li><p><strong>Treat evaluation datasets as first-class artifacts</strong> — Version control them, review changes, and grow them over time.</p>
</li>
<li><p><strong>Make evaluation a team sport</strong> — Product, engineering, and domain experts should all contribute test cases and evaluation criteria.</p>
</li>
</ul>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Every developer has felt the relief of seeing "all tests passing." In AI systems, that reassurance is often misleading. A model can deploy successfully, meet performance benchmarks, and still produce incorrect, incomplete, or misleading outputs in ways traditional tests miss.</p>
<p>The evaluation flywheel addresses this gap by making model behavior testable in practice. Instead of assuming correctness, it forces the system to answer real questions, measures the quality of those answers, and highlights where performance degrades over time. This shifts evaluation from a one-off validation step into an ongoing part of development.</p>
<p>Evaluation won't eliminate uncertainty completely, but it makes failures visible before they reach users. With failures clearly exposed, teams stop guessing and start fixing based on results. This might mean adjusting prompts, improving retrieval logic, or refining evaluation criteria. Over time, this leads to AI systems that evolve in controlled ways rather than breaking silently.</p>
<p><strong>Resources for further reading</strong></p>
<ul>
<li><p><strong>Anthropic's eval guide</strong>: <a target="_blank" href="https://docs.anthropic.com/en/docs/build-with-claude/develop-tests">https://docs.anthropic.com/en/docs/build-with-claude/develop-tests</a></p>
</li>
<li><p><strong>OpenAI's evals framework</strong>: <a target="_blank" href="https://github.com/openai/evals">https://github.com/openai/evals</a></p>
</li>
<li><p><strong>LangChain evaluation</strong>: <a target="_blank" href="https://python.langchain.com/docs/guides/evaluation">https://python.langchain.com/docs/guides/evaluation</a></p>
</li>
<li><p><strong>Arize AI blog</strong>: Comprehensive resources on ML observability</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Reduce Latency in Your Generative AI Apps with Gemini and Cloud Run ]]>
                </title>
                <description>
                    <![CDATA[ You've built your first Generative AI feature. Now what? When deploying AI, the challenge is no longer if the model can answer, but how fast it can answer for a user halfway across the globe. Low latency is not a luxury, it's a requirement for good u... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-reduce-latency-in-your-generative-ai-apps-with-gemini-and-cloud-run/</link>
                <guid isPermaLink="false">69398520ef68a953062588d1</guid>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Load Balancing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Amina Lawal ]]>
                </dc:creator>
                <pubDate>Wed, 10 Dec 2025 14:35:12 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765370930321/e4256d2f-cab3-4ae3-9486-c6651e363366.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>You've built your first Generative AI feature. Now what? When deploying AI, the challenge is no longer <em>if</em> the model can answer, but <em>how fast</em> it can answer for a user halfway across the globe. Low latency is not a luxury, it's a requirement for good user experience.</p>
<p>Today, we’ve moved beyond simple container deployments and into building <strong>Global AI Architectures</strong>. This setup leverages Google’s infrastructure to deliver context-aware, instant Gen AI responses anywhere in the world. If you're ready to get your hands dirty, let's build the future of global, intelligent features.</p>
<p>In this article, you’re not just going to deploy a container, you’ll be building a global AI architecture.</p>
<p>A global AI architecture is a design pattern that leverages a worldwide network to deploy and manage AI services, ensuring the fastest possible response time (low latency) for users, no matter where they are located. Instead of deploying a feature to a single region, this architecture distributes the service across multiple continents.</p>
<p>Most people may deploy a service to a single region. That’s fine for a local user, but physical distance, and the speed of light, creates terrible latency for everyone else. We are going to eliminate this problem by leveraging Google’s global network to deploy the service in a "triangle" of locations.</p>
<p>The generative AI service you’ll be building is a "Local Guide." This application will be designed to be deeply <strong>hyper-personalized</strong>, changing its personality and providing recommendations based on the user's detected geographical context. For example, if a user is in Paris, the guide will greet them warmly, mentioning their city and suggesting a local activity.</p>
<p>You’re going to build this service to achieve three critical goals:</p>
<ul>
<li><p><strong>Lives Almost Everywhere:</strong> Deployed to three continents simultaneously (USA, Europe, and Asia).</p>
</li>
<li><p><strong>Feels Instant:</strong> Uses Google's global fiber network and Anycast IP to route users to the nearest server, ensuring the lowest possible latency.</p>
</li>
<li><p><strong>Knows Where You Are:</strong> Automatically detects the user's location (without relying on client-side GPS permissions) to provide deeply personalized, location-aware suggestions.</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-phase-1-the-location-aware-code">Phase 1: The "Location-Aware" Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-phase-2-build-amp-push">Phase 2: Build &amp; Push</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-phase-3-the-triangle-deployment">Phase 3: The "Triangle" Deployment</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-phase-4-the-global-network-the-glue">Phase 4: The Global Network (The Glue)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-phase-5-testing-teleportation-time">Phase 5: Testing (Teleportation Time)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion-the-global-ai-edge">Conclusion: The Global AI Edge</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To follow along, you need:</p>
<ol>
<li><p><strong>A Google Cloud Project</strong> (with billing enabled).</p>
</li>
<li><p><strong>Google Cloud Shell</strong> (Recommended! No local setup required). Click the icon in the top right of the GCP Console that looks like a terminal prompt <code>&gt;_</code>.</p>
</li>
</ol>
<p><strong>Note</strong>: The project utilizes various Google Cloud services (Cloud Run, Artifact Registry, Load Balancer, Vertex AI), all of which require a Google Cloud Project with billing enabled to function. While many of these services offer a free tier, you must link a billing account to your project. Although a billing account is required, new Google Cloud users may be eligible for a <a target="_blank" href="https://console.cloud.google.com/freetrial?hl=en&amp;facet_utm_source=google&amp;facet_utm_campaign=%28organic%29&amp;facet_utm_medium=organic&amp;facet_url=https%3A%2F%2Fcloud.google.com%2Fsignup-faqs"><strong>free trial credit</strong></a> that should cover the cost of this lab. <a target="_blank" href="https://cloud.google.com/free/docs/free-cloud-features#free-trial">See credit program eligibility and coverage</a></p>
<h2 id="heading-phase-1-the-location-aware-code"><strong>Phase 1: The "Location-Aware" Code</strong></h2>
<p>We don’t want to build a generic chatbot, so we’ll be building a "Local Guide" that changes its personality based on where the request comes from.</p>
<h3 id="heading-enable-the-apis"><strong>Enable the APIs</strong></h3>
<p>To wake up the services, run this in your terminal:</p>
<pre><code class="lang-bash">gcloud services <span class="hljs-built_in">enable</span> \
  run.googleapis.com \
  artifactregistry.googleapis.com \
  compute.googleapis.com \
  aiplatform.googleapis.com \
  cloudbuild.googleapis.com
</code></pre>
<p>This command enables the necessary Google Cloud APIs for the project:</p>
<ul>
<li><p>Cloud Run (<a target="_blank" href="http://run.googleapis.com">run.googleapis.com</a>)</p>
</li>
<li><p>Artifact Registry (<a target="_blank" href="http://artifactregistry.googleapis.com">artifactregistry.googleapis.com</a>)</p>
</li>
<li><p>Compute Engine (<a target="_blank" href="http://compute.googleapis.com">compute.googleapis.com</a>)</p>
</li>
<li><p>Vertex AI (<a target="_blank" href="http://aiplatform.googleapis.com">aiplatform.googleapis.com</a>)</p>
</li>
<li><p>Cloud Build (<a target="_blank" href="http://cloudbuild.googleapis.com">cloudbuild.googleapis.com</a>).</p>
</li>
</ul>
<p>Enabling them ensures that the services we need are ready to be used.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764156603095/fb2ffd56-12e4-4b9f-ac2d-8fbb30fc0a2d.png" alt="Screenshot showing the Google Cloud APIs being successfully completed" class="image--center mx-auto" width="2132" height="280" loading="lazy"></p>
<h3 id="heading-create-and-populate-mainpyhttpmainpy">Create and Populate <a target="_blank" href="http://main.py"><code>main.py</code></a></h3>
<p>This is the brain of our service. In your Cloud Shell terminal, create a file named <a target="_blank" href="http://main.py"><code>main.py</code></a> and paste the following code into it:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, request, jsonify
<span class="hljs-keyword">import</span> vertexai
<span class="hljs-keyword">from</span> vertexai.generative_models <span class="hljs-keyword">import</span> GenerativeModel

app = Flask(__name__)

<span class="hljs-comment"># Initialize Vertex AI</span>
PROJECT_ID = os.environ.get(<span class="hljs-string">"GOOGLE_CLOUD_PROJECT"</span>)
vertexai.init(project=PROJECT_ID)

<span class="hljs-meta">@app.route("/", methods=["GET", "POST"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">generate</span>():</span>
    <span class="hljs-comment"># 1. Identify where the code is physically running (We set this ENV var later)</span>
    service_region = os.environ.get(<span class="hljs-string">"SERVICE_REGION"</span>, <span class="hljs-string">"unknown-region"</span>)

    <span class="hljs-comment"># 2. Identify where the user is (Header comes from Global Load Balancer)</span>
    <span class="hljs-comment"># Format typically: "City,State,Country"</span>
    user_location = request.headers.get(<span class="hljs-string">"X-Client-Geo-Location"</span>, <span class="hljs-string">"Unknown Location"</span>)

    model = GenerativeModel(<span class="hljs-string">"gemini-2.5-flash"</span>)

    <span class="hljs-comment"># 3. Construct a location-aware prompt</span>
    prompt = (
        <span class="hljs-string">f"You are a helpful local guide. The user is currently in <span class="hljs-subst">{user_location}</span>. "</span>
        <span class="hljs-string">"Greet them warmly mentioning their city, and suggest one "</span>
        <span class="hljs-string">"hidden gem activity to do nearby right now. Keep it under 50 words."</span>
    )

    <span class="hljs-keyword">try</span>:
        response = model.generate_content(prompt)
        <span class="hljs-keyword">return</span> jsonify({
            <span class="hljs-string">"ai_response"</span>: response.text,
            <span class="hljs-string">"meta"</span>: {
                <span class="hljs-string">"served_from_region"</span>: service_region,
                <span class="hljs-string">"user_detected_location"</span>: user_location
            }
        })
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        <span class="hljs-keyword">return</span> jsonify({<span class="hljs-string">"error"</span>: str(e)}), <span class="hljs-number">500</span>

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    app.run(debug=<span class="hljs-literal">True</span>, host=<span class="hljs-string">"0.0.0.0"</span>, port=int(os.environ.get(<span class="hljs-string">"PORT"</span>, <span class="hljs-number">8080</span>)))
</code></pre>
<p>It’s a simple Flask web application that relies entirely on a specific HTTP header (<code>X-Client-Geo-Location</code>) that the global load balancer will inject later in the process. This design choice keeps the Python code clean, fast, and focused on using the context that the powerful Google Cloud infrastructure provides. The script uses Vertex AI and the high-performance Gemini 2.5 Flash generative model.</p>
<p>This core logic of the application is a simple Flask web service. It does the following:</p>
<ul>
<li><p><strong>Initialization:</strong> Sets up the Flask app, logging, and initializes the Vertex AI client using the project ID.</p>
</li>
<li><p><strong>Context:</strong> It extracts two critical pieces of information: the <code>SERVICE_REGION</code> (where the code is physically running) from the environment variable, and the <code>X-Client-Geo-Location</code> (the user's detected location) from the request header, which will be injected by the global load balancer.</p>
</li>
<li><p><strong>AI Generation:</strong> It uses the high-performance <code>gemini-2.5-flash</code> model.</p>
</li>
<li><p><strong>Prompt Construction:</strong> A dynamic, location-aware prompt is built using the detected city to instruct Gemini to act as a helpful local guide and provide a personalized suggestion.</p>
</li>
<li><p><strong>Response:</strong> The response includes the AI's generated text and a <code>meta</code> section containing both the serving region and the user's detected location, which helps in verification.</p>
</li>
</ul>
<h3 id="heading-create-the-dockerfile"><strong>Create the</strong> <code>Dockerfile</code></h3>
<p>This Dockerfile tells Cloud Run how to build the Python application into a container image. Create a file named <code>Dockerfile</code> in the same directory as <code>main.py</code> and paste the following content into it:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.9</span>-slim

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> main.py .</span>

<span class="hljs-comment"># Install Flask and Vertex AI SDK</span>
<span class="hljs-keyword">RUN</span><span class="bash"> pip install flask google-cloud-aiplatform</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"python"</span>, <span class="hljs-string">"main.py"</span>]</span>
</code></pre>
<p>Here’s what the code does:</p>
<ul>
<li><p>Starts with a lightweight Python base image <code>python:3.9-slim</code>.</p>
</li>
<li><p>Sets the working directory inside the container <code>WORKDIR /app</code>.</p>
</li>
<li><p>Copies your application code into the container.</p>
</li>
<li><p><code>RUN pip install...</code> installs the required Python packages: Flask for the web server and <code>google-cloud-aiplatform</code> for accessing the Gemini model.</p>
</li>
<li><p><code>CMD</code> specifies the command to run when the container starts.</p>
</li>
</ul>
<h2 id="heading-phase-2-build-amp-push"><strong>Phase 2: Build &amp; Push</strong></h2>
<p>Let's package this up. For efficiency and consistency, we’ll follow the best practice of Build Once, Deploy Many. We’ll build the container image once using Cloud Build and store it in Google's Artifact Registry. This guarantees that the same tested application code runs in New York, Belgium, and Tokyo.</p>
<p>First, sets an environment variable for your Google Cloud Project ID to simplify later commands.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># 1. Set your Project ID variable</span>
<span class="hljs-built_in">export</span> PROJECT_ID=$(gcloud config get-value project)
</code></pre>
<p>Then create a new Docker repository named <code>gemini-global-repo</code> in the <code>us-central1</code> region to store the application container image:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># 2. Create the repository</span>
gcloud artifacts repositories create gemini-global-repo \
    --repository-format=docker \
    --location=us-central1 \
    --description=<span class="hljs-string">"Repo for Global Gemini App"</span>
</code></pre>
<p>Using the <code>mkdir gemini-app</code> command, create and navigate into a directory where you should place your <a target="_blank" href="http://main.py"><code>main.py</code></a> and <code>Dockerfile</code>:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># 3. Prepare the Build Environment (Crucial Step! 💡). To ensure the build process only includes our necessary code and avoids including temporary files from Cloud Shell's home directory </span>
mkdir gemini-app
<span class="hljs-built_in">cd</span> gemini-app
</code></pre>
<p>Next, use <code>gcloud builds submit --tag</code> to build the container image from the files in the current directory and push the resulting image to the newly created Artifact Registry repository:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># 4. Build the image (This takes about 2 minutes)</span>
gcloud builds submit --tag us-central1-docker.pkg.dev/<span class="hljs-variable">$PROJECT_ID</span>/gemini-global-repo/region-ai:v
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764159484475/97a5b2b6-f3c2-4d1b-8bf8-6f302748e744.png" alt="Screenshot of Cloud Shell Editor showing Dockerfile and terminal build output." class="image--center mx-auto" width="2880" height="1348" loading="lazy"></p>
<p><strong>NOTE:</strong> You might notice that we created the Artifact Registry repository (<code>gemini-global-repo</code>) in the <code>us-central1</code> region. This choice is purely for management and storage of the container image. When you create an image and push it to a regional Artifact Registry, the resulting image is still accessible globally. For this lab, <code>us-central1</code> serves as a reliable, central location for our single, canonical container image, the single source of truth, which is then pulled by Cloud Run in the three separate global regions.</p>
<h2 id="heading-phase-3-the-triangle-deployment"><strong>Phase 3: The "Triangle" Deployment</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764661657796/0890a47b-589a-4cf8-b537-bb61e5e65ee7.png" alt="Diagram of the Global AI Architecture Triangle Deployment." class="image--center mx-auto" width="1024" height="1024" loading="lazy"></p>
<p>We’ll deploy the same image to three corners of the world, forming our "Triangle". This ensures that whether a user is in Lagos, London, or Tokyo, they’ll be geographically close to a server. This is the low-latency core of our architecture.</p>
<p>We’ll use Cloud Run to deploy our services. Cloud Run is a fully managed serverless platform on Google Cloud that enables you to run stateless containers via web requests or events. Crucially, it is serverless, meaning you don't manage any virtual machines, operating system updates, or scaling infrastructure. You provide a container image, and Cloud Run automatically scales it up (and down to zero) in the region you specify.</p>
<p>For this project, we’ll use its regional deployment capability to easily and consistently deploy the exact same container image to New York, Belgium, and Tokyo.</p>
<p><strong>Note:</strong> Setting it up primarily involves enabling the API (done in Phase 1) and using the <code>gcloud run deploy</code> command, which handles provisioning and managing the service in the specified region.</p>
<p>Now, we’ll proceed to deploy the single, canonical container image to three separate Cloud Run regions, forming the "Triangle Deployment".</p>
<p>First, set a variable for the image path, pointing to the image stored in Artifact Registry.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Define our image URL</span>
<span class="hljs-built_in">export</span> IMAGE_URL=us-central1-docker.pkg.dev/<span class="hljs-variable">$PROJECT_ID</span>/gemini-global-repo/region-ai:v1
</code></pre>
<pre><code class="lang-bash">
<span class="hljs-comment"># 1. Deploy to USA (New York)</span>
gcloud run deploy gemini-service \
    --image <span class="hljs-variable">$IMAGE_URL</span> \
    --region us-east4 \
    --set-env-vars SERVICE_REGION=us-east4 \
    --allow-unauthenticated

<span class="hljs-comment"># 2. Deploy to Europe (Belgium)</span>
gcloud run deploy gemini-service \
    --image <span class="hljs-variable">$IMAGE_URL</span> \
    --region europe-west1 \
    --set-env-vars SERVICE_REGION=europe-west1 \
    --allow-unauthenticated

<span class="hljs-comment"># 3. Deploy to Asia (Tokyo)</span>
gcloud run deploy gemini-service \
    --image <span class="hljs-variable">$IMAGE_URL</span> \
    --region asia-northeast1 \
    --set-env-vars SERVICE_REGION=asia-northeast1 \
    --allow-unauthenticated
</code></pre>
<p><code>gcloud run deploy gemini-service...</code> deploys the service. Key flags:</p>
<ul>
<li><p><code>--image \$IMAGE_URL</code> specifies the container image to use.</p>
</li>
<li><p><code>--region</code> specifies the deployment region (for example, <code>us-east4</code> for New York).</p>
</li>
<li><p><code>--set-env-vars SERVICE_REGION=...</code> injects an environment variable into the running container to let the <a target="_blank" href="http://main.py"><code>main.py</code></a> code know its own physical region.</p>
</li>
<li><p><code>--allow-unauthenticated</code> makes the service publicly accessible, as required for the Load Balancer to connect.</p>
</li>
</ul>
<p><strong>Note:</strong> The commands are repeated for Europe (<code>europe-west1</code>) and Asia (<code>asia-northeast1</code>) regions.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764160600271/fbb6a810-7496-4b29-a405-b67a22a988ed.png" alt="Screenshot of Cloud Shell terminal showing the execution of the cloud run services." class="image--right mx-auto mr-0" width="2880" height="1348" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764160624375/dd4dc7e7-22a9-4d8b-a36c-7a0988068f57.png" alt="Cloud run Service Url (asia region) terminal screenshot showing the successful execution of the service" class="image--center mx-auto" width="2880" height="536" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764160656898/1b6ca938-9ce4-48f6-bb3b-d09900dbde68.png" alt="Cloud run Service Url (europe region) terminal screenshot showing the successful execution of the service" class="image--center mx-auto" width="2880" height="536" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764160665595/39c2524d-62c8-4187-8b8f-15f7ebbffba4.png" alt="Cloud run Service Url (us-east region) terminal screenshot showing the successful execution of the service" class="image--center mx-auto" width="2880" height="536" loading="lazy"></p>
<p><code>user_detected_location</code> is always "Unknown Location". This is expected. You are accessing the Cloud Run URLs directly, not via the global load balancer, so the <code>X-Client-Geo-Location</code> header is not yet being injected.</p>
<h2 id="heading-phase-4-the-global-network-the-glue"><strong>Phase 4: The Global Network (The Glue)</strong></h2>
<p>You are now ready to execute the steps to create the <strong>Global External HTTP Load Balancer</strong> infrastructure. This is the "magic" that stitches the three regional services together behind a single <strong>Anycast IP Address</strong>. The load balancer performs two critical functions:</p>
<ol>
<li><p><strong>Global Routing:</strong> It uses Google’s high-speed network to automatically route the user to the closest available region (for example, Tokyo user → Asia service).</p>
</li>
<li><p><strong>Context Injection:</strong> It dynamically adds the <code>X-Client-Geo-Location</code> header to the request, telling your code exactly where the user is<sup>.</sup></p>
</li>
</ol>
<h3 id="heading-the-global-ip"><strong>The Global IP</strong></h3>
<p><code>gcloud compute addresses create...</code> creates a single, global, static Anycast IP address (<code>gemini-global-ip</code>) that will serve as the single public entry point for users worldwide. That is</p>
<pre><code class="lang-bash">gcloud compute addresses create gemini-global-ip \
    --global \
    --ip-version IPV4
</code></pre>
<h3 id="heading-the-network-endpoint-groups-negs"><strong>The Network Endpoint Groups (NEGs)</strong></h3>
<p><code>gcloud compute network-endpoint-groups create...</code> creates a <strong>Serverless Network Endpoint Group (NEG)</strong> for each regional Cloud Run deployment. For example, <code>neg-us</code> is created in <code>us-east4</code> and points to the <code>gemini-service</code> in that region. These map your Cloud Run services to the Load Balancer's backend service:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># USA NEG</span>
gcloud compute network-endpoint-groups create neg-us \
    --region=us-east4 \
    --network-endpoint-type=serverless  \
    --cloud-run-service=gemini-service

<span class="hljs-comment"># Europe NEG</span>
gcloud compute network-endpoint-groups create neg-eu \
    --region=europe-west1 \
    --network-endpoint-type=serverless \
    --cloud-run-service=gemini-service

<span class="hljs-comment"># Asia NEG</span>
gcloud compute network-endpoint-groups create neg-asia \
    --region=asia-northeast1 \
    --network-endpoint-type=serverless \
    --cloud-run-service=gemini-service
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764161003478/549c959d-8ab5-45d6-a2ae-94129529b5b4.png" alt="Screenshot of Cloud Shell terminal showing the execution of global load balancer setup commands." class="image--center mx-auto" width="2880" height="1010" loading="lazy"></p>
<h3 id="heading-the-backend-service-amp-routing"><strong>The Backend Service &amp; Routing</strong></h3>
<p>This is the load balancer's core, distributing traffic across your regions. Connect the NEGs to a global backend.</p>
<p><code>gcloud compute backend-services create...</code> creates the global backend service (<code>gemini-backend-global</code>), which is the core component that manages traffic distribution:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create the backend service</span>
gcloud compute backend-services create gemini-backend-global \
    --global \
    --protocol=HTTP
</code></pre>
<p><code>gcloud compute backend-services add-backend...</code> adds all three regional NEGs (<code>neg-us</code>, <code>neg-eu</code>, <code>neg-asia</code>) as backends to the global service. This tells the load balancer where all the services are located:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Add the 3 regions to the backend</span>
gcloud compute backend-services add-backend gemini-backend-global \
    --global --network-endpoint-group=neg-us --network-endpoint-group-region=us-east4
gcloud compute backend-services add-backend gemini-backend-global \
    --global --network-endpoint-group=neg-eu --network-endpoint-group-region=europe-west1
gcloud compute backend-services add-backend gemini-backend-global \
    --global --network-endpoint-group=neg-asia --network-endpoint-group-region=asia-northeast1
</code></pre>
<h3 id="heading-the-url-map-amp-frontend"><strong>The URL Map &amp; Frontend</strong></h3>
<p>Now we can finalize the connection.</p>
<p><code>gcloud compute url-maps create...</code> creates a URL Map (<code>gemini-url-map</code>) to direct all incoming traffic to the Backend Service:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create URL Map (Maps incoming requests to the backend service)</span>
gcloud compute url-maps create gemini-url-map \
    --default-service gemini-backend-global
</code></pre>
<p><code>gcloud compute target-http-proxies create...</code> creates an HTTP Proxy (<code>gemini-http-proxy</code>) that inspects the request and directs it based on the URL map.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create HTTP Proxy (The component that inspects the request headers)</span>
gcloud compute target-http-proxies create gemini-http-proxy \
    --url-map gemini-url-map
</code></pre>
<p><code>export VIP=...</code> retrieves the final, public IP address of the newly created Global IP and stores it in the <code>VIP</code> environment variable.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Get your IP Address variable</span>
<span class="hljs-built_in">export</span> VIP=$(gcloud compute addresses describe gemini-global-ip --global --format=<span class="hljs-string">"value(address)"</span>)
</code></pre>
<p><code>gcloud compute forwarding-rules create...</code> creates the final global Forwarding Rule (<code>gemini-forwarding-rule</code>). This links the Global IP (<code>$VIP</code>) to the HTTP Proxy and opens port 80 for public traffic.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create Forwarding Rule (Open port 80)</span>
gcloud compute forwarding-rules create gemini-forwarding-rule \
    --address=<span class="hljs-variable">$VIP</span> \
    --global \
    --target-http-proxy=gemini-http-proxy \
    --ports=80
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764161323862/299c6c43-9074-493c-95b1-2c08208aa2ec.png" alt="Cloud Shell terminal screenshot showing the successful execution of commands to create the gemini-backend-global service" class="image--center mx-auto" width="2880" height="1010" loading="lazy"></p>
<h2 id="heading-phase-5-testing-teleportation-time"><strong>Phase 5: Testing (Teleportation Time)</strong></h2>
<p>Global load balancers take about <strong>5-7 minutes</strong> to propagate worldwide. This is how you verify that the global load balancer is working correctly:</p>
<ul>
<li><p>Using the single <strong>VIP</strong> (Virtual IP) address.</p>
</li>
<li><p><strong>Routing traffic</strong> to the nearest server.</p>
</li>
<li><p><strong>Injecting the</strong> <code>X-Client-Geo-Location</code> header to tell your code where the user is.</p>
</li>
</ul>
<h3 id="heading-1-get-your-global-ip"><strong>1. Get your Global IP</strong></h3>
<p>First, ensure your <code>VIP</code> variable is set and retrieve the final address:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"http://<span class="hljs-variable">$VIP</span>/"</span>
</code></pre>
<p>The output will be your single point of entry for the entire global architecture.</p>
<h3 id="heading-2-test-teleportation"><strong>2. Test "Teleportation"</strong></h3>
<p>These <code>curl</code> commands simulate a user requesting the service from different geographical locations by manually injecting the <code>X-Client-Geo-Location</code> header. This bypasses the need to be physically in those locations for testing.</p>
<h4 id="heading-simulate-europe-paris">Simulate Europe (Paris)</h4>
<p>We expect this to be served by the <code>europe-west1</code> region because it's the closest server.</p>
<pre><code class="lang-bash">curl -H <span class="hljs-string">"X-Client-Geo-Location: Paris,France"</span> http://<span class="hljs-variable">$VIP</span>/
</code></pre>
<p><em>Expected Output:</em> Gemini should say "Bonjour" and mention Paris. The <code>served_from_region</code> should be <code>europe-west1</code>.</p>
<p><strong>Simulate Asia (Tokyo)</strong></p>
<p>We expect this to be served by the <code>asia-northeast1</code> region.</p>
<pre><code class="lang-bash">curl -H <span class="hljs-string">"X-Client-Geo-Location: Tokyo,Japan"</span> http://<span class="hljs-variable">$VIP</span>/
</code></pre>
<p><em>Expected Output:</em> Gemini should mention Tokyo. The <code>served_from_region</code> should be <code>asia-northeast1</code>.</p>
<h4 id="heading-simulate-usa-new-york">Simulate USA (New York)</h4>
<p>We expect this to be served by the <code>us-east4</code> region.</p>
<pre><code class="lang-bash">curl -s -H <span class="hljs-string">"X-Client-Geo-Location: New York,USA"</span> http://<span class="hljs-variable">$VIP</span>/ | jq .
</code></pre>
<p><em>Expected Output:</em> Gemini should mention USA. The <code>served_from_region</code> should be <code>us-east4</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764161891891/ecc290ef-1c75-4088-b453-093a92b404ff.png" alt="Cloud Shell terminal screenshot showing the results of curl commands simulating users in Paris, Tokyo, and New York." class="image--center mx-auto" width="2880" height="1010" loading="lazy"></p>
<p><strong>Note:</strong> The <code>| jq .</code> part is optional, but highly recommended as it formats the JSON output, making it much easier to read the <code>served_from_region</code> and <code>ai_response</code> details. If <code>jq</code> isn't available, you can just run <code>curl ...</code> without it.</p>
<h2 id="heading-conclusion-the-global-ai-edge">Conclusion: The Global AI Edge</h2>
<p>Congratulations! You have successfully built a sophisticated, global AI architecture that solves the challenges of latency and personalization for generative AI features. By combining the following technologies, you achieved two critical outcomes:</p>
<ul>
<li><p><strong>Guaranteed Low Latency:</strong> By deploying the <strong>Cloud Run</strong> service to a "Triangle" of global regions (USA, Europe, Asia) and using the <strong>Global External HTTP Load Balancer's Anycast IP</strong>, your users are automatically routed across Google’s private fiber network to the closest available server.</p>
</li>
<li><p><strong>Hyper-Personalization:</strong> The global load balancer was configured to dynamically inject the user's geographical location via the <code>X-Client-Geo-Location</code> header. This context was passed directly to the <strong>Gemini 2.5 Flash</strong> model, allowing it to act as a truly location-aware "Local Guide".</p>
</li>
</ul>
<p>This pattern allows you to scale intelligent features globally and is immediately applicable to any application where speed and context are essential, from real-time translations to hyper-local recommendations.</p>
<h3 id="heading-cleanup"><strong>Cleanup</strong></h3>
<p>Don't leave the meter running! Remember to execute the cleanup commands to ensure you don't incur unnecessary charges</p>
<pre><code class="lang-bash">gcloud run services delete gemini-service --region us-east4 --quiet
gcloud run services delete gemini-service --region europe-west1 --quiet
gcloud run services delete gemini-service --region asia-northeast1 --quiet
gcloud compute forwarding-rules delete gemini-forwarding-rule --global --quiet
gcloud compute addresses delete gemini-global-ip --global --quiet
gcloud compute backend-services delete gemini-backend-global --global --quiet
gcloud compute url-maps delete gemini-url-map --global --quiet
gcloud compute target-http-proxies delete gemini-http-proxy --global --quiet
</code></pre>
<h3 id="heading-resources">Resources</h3>
<ul>
<li><p>Google Cloud Shell Documentation</p>
</li>
<li><p><a target="_blank" href="https://www.google.com/search?q=https://cloud.google.com/vertex-ai/docs/generative-ai/learn/sdk">Vertex AI Generative AI SDK</a></p>
</li>
<li><p><a target="_blank" href="https://cloud.google.com/artifact-registry/docs">Artifact Registry Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://cloud.google.com/run/docs">Cloud Run Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://www.google.com/search?q=https://cloud.google.com/load-balancing/docs/load-balancing-overview%23external_http_s_load_balancing">Global External HTTP(S) Load Balancer Overview</a></p>
</li>
<li><p><a target="_blank" href="https://www.google.com/search?q=https://cloud.google.com/load-balancing/docs/negs/serverless-neg-overview">Serverless Network Endpoint Groups (NEGs)</a></p>
</li>
<li><p><a target="_blank" href="https://docs.cloud.google.com/run/docs/multiple-regions">Serve traffic from multiple regions</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ A Game Developer’s Guide to Understanding Screen Resolution ]]>
                </title>
                <description>
                    <![CDATA[ Every game developer obsesses over performance, textures, and frame rates, but resolution is the quiet foundation that makes or breaks visual quality.  Whether you are building a pixel-art indie game or a high-fidelity 3D world, understanding how res... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/a-game-developers-guide-to-understanding-screen-resolution/</link>
                <guid isPermaLink="false">691de96a0dec4f292a0f8ff0</guid>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Accessibility ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Wed, 19 Nov 2025 15:59:38 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763567809746/3fb2c926-9602-4765-9ef4-5ea565e0e148.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every game developer obsesses over performance, textures, and frame rates, but resolution is the quiet foundation that makes or breaks visual quality. </p>
<p>Whether you are building a pixel-art indie game or a high-fidelity 3D world, understanding how resolution works is essential. </p>
<p>It affects how your art assets scale, how your UI appears, and how your game feels on different screens. Yet, many developers still treat resolution as a simple number instead of a design decision.</p>
<p>Let’s learn what resolutions are and why it matters for game developers. </p>
<h2 id="heading-what-we-will-cover">What we will Cover</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-resolution-really-means">What Resolution Really Means</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-evolution-of-resolution-in-gaming">The Evolution of Resolution in Gaming</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-dpi-scaling-and-texture-clarity">DPI, Scaling, and Texture Clarity</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-resolution-vs-performance">Resolution vs. Performance</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-aspect-ratio-and-display-diversity">Aspect Ratio and Display Diversity</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-art-of-testing-in-4k-and-hdr">The Art of Testing in 4K and HDR</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-preparing-for-next-gen-displays">Preparing for Next-Gen Displays</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-resolution-really-means">What Resolution Really Means</h2>
<p>Resolution defines how many pixels a screen can display horizontally and vertically.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763470514266/2ba4689a-6e8d-423d-8da7-694bf7bc6d9e.png" alt="Screen Resolution Sizes" class="image--center mx-auto" width="1000" height="428" loading="lazy"></p>
<p>A monitor labelled 1920x1080 has 1920 pixels across and 1080 down, which equals over two million pixels in total. More pixels mean more visual detail but also more rendering work for the GPU.</p>
<p>In game development, that tradeoff is constant. Rendering at higher resolutions improves clarity but reduces frame rates unless your code and assets are optimized. </p>
<p>Many developers solve this by offering resolution scaling options in their games, letting players balance visual quality and performance.</p>
<p>It’s also important to distinguish between screen size and resolution. A 27-inch monitor and a 15-inch laptop can both run at 1080p, but the larger display will have bigger, less dense pixels. </p>
<p>This is where pixel density comes in. High-density displays pack more pixels per inch, creating smoother edges and sharper textures even at the same resolution.</p>
<h2 id="heading-the-evolution-of-resolution-in-gaming">The Evolution of Resolution in Gaming</h2>
<p>Games have evolved alongside display technology. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763514379811/7a5bef4e-5441-4b40-99cb-3d925865ac87.jpeg" alt="Gameplay Resolution" class="image--center mx-auto" width="1920" height="1080" loading="lazy"></p>
<p>Early consoles ran at 240p, then 480p during the SD era. The jump to HD with 720p and 1080p transformed game visuals. Suddenly, developers had to think about anti-aliasing, texture resolution, and UI scaling in new ways.</p>
<p>Today, 4K and HDR have become the standard for modern consoles and PCs. Developers now design with higher fidelity in mind, baking in lighting systems, shaders, and art pipelines that scale up to Ultra HD. </p>
<p>That’s why testing on different display resolutions isn’t just good practice, it’s critical for consistent player experience.</p>
<p>If you want to see how your game performs on large high-resolution displays, try testing it on a modern TV for PS5. These screens are optimized for 4K and 120Hz refresh rates, giving you a realistic look at how your game will appear in a living-room setup. </p>
<p>They also help you spot UI scaling issues, frame pacing problems, and HDR color mismatches that might go unnoticed on a typical monitor.</p>
<h2 id="heading-dpi-scaling-and-texture-clarity">DPI, Scaling, and Texture Clarity</h2>
<p>For web developers, <a target="_blank" href="https://en.wikipedia.org/wiki/Dots_per_inch">DPI</a> mostly affects how images scale. But for game developers, DPI connects directly to texture resolution and how art assets are perceived at different screen sizes. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763470672635/57795a33-7700-4aee-8dd4-aceb8b71dd49.jpeg" alt="DPI Levels" class="image--center mx-auto" width="1200" height="675" loading="lazy"></p>
<p>A sprite that looks crisp on a 1080p monitor might appear tiny or blurry on a 4K display if not properly scaled. Engines like <a target="_blank" href="https://www.freecodecamp.org/news/game-development-for-beginners-unity-course/">Unity</a> and Unreal handle this with dynamic scaling options, but understanding the underlying math helps. </p>
<p>When your display density doubles, each asset needs four times as many pixels to appear at the same size and sharpness. If you do not plan for this, your carefully crafted textures might look soft or misaligned on higher-resolution displays.</p>
<p>This is why UI systems in modern engines rely on resolution-independent units. In Unity, Canvas Scaler helps ensure your interface looks the same on every device. In Unreal, DPI scaling rules allow developers to maintain consistent HUD layouts. Getting this right means your game remains legible on everything from handhelds to 8K TVs.</p>
<h2 id="heading-resolution-vs-performance">Resolution vs Performance</h2>
<p>The biggest cost of higher resolution is GPU load. Rendering in 4K means pushing four times as many pixels as 1080p. Without proper optimization, frame rates can drop sharply. </p>
<p>That’s why many <a target="_blank" href="https://en.wikipedia.org/wiki/AAA_%28video_game_industry%29">AAA games</a> use resolution scaling techniques like temporal upsampling or DLSS. These methods render frames at a lower resolution and then use AI or interpolation to upscale them without losing clarity.</p>
<p>As a developer, you should test your game across multiple resolutions and aspect ratios. This helps ensure your render pipeline, shaders, and assets adapt smoothly. Tools like <a target="_blank" href="https://developer.nvidia.com/nsight-systems">NVIDIA Nsight</a> or Unreal’s built-in profiler show how resolution affects frame time and GPU usage.</p>
<p>If your game includes video content or cinematic sequences, also remember that video compression behaves differently at higher resolutions. Encoding 4K video requires significantly more bandwidth and storage, which can affect your build size and performance during playback.</p>
<h2 id="heading-aspect-ratio-and-display-diversity">Aspect Ratio and Display Diversity</h2>
<p>Aspect ratio determines the shape of the display.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763476458560/52decf37-c4f4-4927-96b8-1c6fd9be074c.jpeg" alt="Aspect Ratios" class="image--center mx-auto" width="2560" height="977" loading="lazy"></p>
<p>Most modern games target 16:9, but 21:9 ultrawide and 32:9 super-ultrawide displays are becoming more popular. Developers must ensure their camera framing and UI layouts adapt accordingly.</p>
<p>When a game is locked to one ratio, black bars or stretching can occur. To fix this, adjust your camera’s field of view dynamically or provide safe viewport settings.</p>
<p>Engines like Unreal let you script these adjustments easily, while Unity’s Cinemachine system handles FOV scaling automatically.</p>
<p>Even TVs now vary in aspect ratio capabilities, especially with new mini LED and OLED technologies. Testing across multiple ratios ensures your game looks balanced and cinematic on every screen.</p>
<h2 id="heading-the-art-of-testing-in-4k-and-hdr">The Art of Testing in 4K and HDR</h2>
<p>4K and HDR introduce new layers of visual complexity. HDR displays show a wider range of brightness and color depth, which means lighting and textures can look completely different compared to SDR monitors. To handle this, calibrate your color grading pipeline and use tone mapping tools within your engine.</p>
<p>When working with HDR assets, always test your output on real hardware. Emulators and monitors often fail to reproduce true HDR contrast. A proper HDR-certified TV helps you identify overexposure, color clipping, and banding issues before release.</p>
<h2 id="heading-preparing-for-next-gen-displays">Preparing for Next-Gen Displays</h2>
<p>The display industry continues to evolve fast. 8K and high refresh rate panels are already entering mainstream markets. </p>
<p>For developers, this means thinking ahead. Designing scalable rendering systems, supporting dynamic resolution, and maintaining flexible UI layouts are now essential parts of modern game design.</p>
<p>As displays get sharper, player expectations rise too. Textures, shaders, and post-processing all need to support higher levels of detail without compromising performance. By understanding how resolution interacts with your pipeline, you can future-proof your games for years to come.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Resolution is more than a number on a settings menu. It is a design constraint, a performance factor, and a creative opportunity. As a game developer, mastering resolution helps you build experiences that look sharp, play smoothly, and scale across every device.</p>
<p>The next time you polish your textures or fine-tune your rendering settings, remember that every pixel counts. Understanding how resolution, scaling, and density interact will not only make your games more beautiful but also more accessible to every player, whether they’re gaming on a laptop, a monitor, or the living-room tv that brings your visuals to life in stunning detail.</p>
<p><em>Hope you enjoyed this article. Find me on</em> <a target="_blank" href="https://linkedin.com/in/manishmshiva"><em>Linkedin</em></a> <em>or</em> <a target="_blank" href="https://manishshivanandhan.com/"><em>visit my website</em></a><em>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Reduce Round Trip Time (RTT) with Next.js ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever wondered why some websites load almost immediately and others leave you looking at a blank screen, even when your internet connection is fast? In some cases, your internet speed may not be the issue. It is usually because of Round Trip ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-reduce-round-trip-time-rtt-with-nextjs/</link>
                <guid isPermaLink="false">690c78446959d1f714d81d45</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ web performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chukwudi Nweze ]]>
                </dc:creator>
                <pubDate>Thu, 06 Nov 2025 10:28:20 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762424304223/4d818ff7-0fe2-448d-8acd-3da092bc55a4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever wondered why some websites load almost immediately and others leave you looking at a blank screen, even when your internet connection is fast? In some cases, your internet speed may not be the issue. It is usually because of Round Trip Time (RTT), which is how long it takes your browser to send a request to a server and get a response.</p>
<p>The internet depends on physical infrastructure: fiber-optic cables, satellites, and data centers often located thousands of kilometers away. Network requests travel at high speed, but they are still limited by the speed of light (around 300,000 km/s). For instance, a network request from Lagos, Nigeria to a server in San Francisco, USA travels more than 12,000 km, and takes about 150–200 milliseconds for a single round trip under ideal conditions. Multiply that by the 20–30 requests a typical web page makes (for HTML, CSS, images, APIs and more), and those milliseconds quickly add up to seconds of delay before a page fully loads.</p>
<p>In this article, we’ll explain in detail what Round Trip Time(RTT) is, why it is one of the most overlooked factors in web performance, and how you can use Next.js to minimize the number of RTTs to make your applications feel fast and responsive. You will learn how features like Server-Side Rendering (SSR), React Server Components (RSC), image optimization, and caching all work together to reduce Round Trip Time in a web page.</p>
<h2 id="heading-what-you-will-learn"><strong>What You will Learn</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-you-will-learn">What You will Learn</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-round-trip-time-rtt">What is Round Trip Time (RTT)?</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-how-distance-increases-round-trip-time">How Distance Increases Round Trip Time</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-round-trip-time-affects-web-performance">How Round Trip Time Affects Web Performance</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-why-client-side-rendering-feels-slower">Why Client-Side Rendering Feels Slower</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-reduce-round-trip-time-with-nextjs">How to Reduce Round Trip Time with Next.js</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-server-side-rendering-ssr">Server-Side Rendering (SSR)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-react-server-components-rsc">React Server Components (RSC)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-image-optimization">Image Optimization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-caching-and-static-rendering">Caching and Static Rendering</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-trade-offs-across-nextjs-rendering-methods">Trade-offs Across Next.js Rendering Methods</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-to-use-each-rendering-method">When to Use Each Rendering Method</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-round-trip-time-rtt">What is Round Trip Time (RTT)?</h2>
<p>When you visit a website, the browser makes a network request to a server. The server processes the request and then sends a response back. The Round Trip Time is the complete duration of this journey in milliseconds, which includes:</p>
<ol>
<li><p><strong>Travel Time</strong>: The amount of time it took the network request to get to the server.</p>
</li>
<li><p><strong>Processing Time</strong>: The amount of time the server took to process the request.</p>
</li>
<li><p><strong>Return Time</strong>: The amount of time it took the response get back to the browser.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761405949671/1ceaadf4-5576-41ae-9f25-e846f41b3e67.png" alt="An illustration showing round-trip time (RTT) communication between a client and a server, where the client request takes about 100 ms to reach the server, and the server response takes about 200 ms to return to the client" class="image--center mx-auto" width="990" height="286" loading="lazy"></p>
<h3 id="heading-how-distance-increases-round-trip-time">How Distance Increases Round Trip Time</h3>
<p>Round Trip Time depends heavily on the physical distance between the client and server. For example:</p>
<ul>
<li><p>A user in Lagos, Nigeria making a network request to a server in London that is about 5,000 km away might see a round trip time of 100–150 ms.</p>
</li>
<li><p>A server in San Francisco that is about 12,000 km could push the roundtrip time to 200–300 ms. The farther the server, the higher the round trip time.</p>
</li>
</ul>
<h2 id="heading-how-round-trip-time-affects-web-performance">How Round Trip Time Affects Web Performance</h2>
<p>Modern web pages make multiple network requests to load completely. Imagine loading an e-commerce product page that requires:</p>
<ul>
<li><p>1 network request for HTML (about 200 ms)</p>
</li>
<li><p>5 network requests for CSS/JavaScript (about 1,000 ms)</p>
</li>
<li><p>10 network requests for images (about 2,000 ms)</p>
</li>
<li><p>4 network requests for product data via API (about 800 ms)</p>
</li>
</ul>
<p>That shows that the product page will take 20 network requests to fully load, which is about 4 seconds of network delay.</p>
<p>The probability of bounce increases 32% as page load time goes from 1 second to 3 seconds (<a target="_blank" href="https://www.thinkwithgoogle.com/marketing-strategies/app-and-mobile/page-load-time-statistics/">Google/SOASTA Research, 2017</a>). That means about one-third of visitors leave before the page even loads.</p>
<h3 id="heading-why-client-side-rendering-feels-slower">Why Client-Side Rendering Feels Slower</h3>
<p>In client side rendering applications, each request adds a round trip time, and traditional client-side rendering (CSR) in React apps increases this:</p>
<ul>
<li><p>The browser downloads a minimal HTML shell and a large JavaScript bundle.</p>
</li>
<li><p>The JavaScript runs to fetch data and render the UI, requiring additional network requests.</p>
</li>
<li><p>Each API call adds another RTT, delaying the First Contentful Paint (FCP).</p>
</li>
</ul>
<p>First Contentful Paint (FCP) measures the time from when the user first navigated to the page to when any part of the page's content such as text, images,<code>&lt;svg&gt;</code> or <code>&lt;canvas&gt;</code> elements is rendered on the screen.</p>
<p>In CSR apps, FCP is delayed because the browser cannot display any meaningful content until JavaScript has finished loading, parsing, and executing the code needed to construct the UI. A regular CSR application may need 5 to 10 network round trips to get all the resources needed for the rendering of the UI, which can easily add several seconds of delay.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// pages/index.js (CSR)</span>
<span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [products, setProducts] = useState([])

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Fetch data after page loads</span>
    fetch(<span class="hljs-string">"https://api.example.com/products"</span>)
      .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> res.json())
      .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> setProducts(data))
  }, [])

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Products<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      {products.length ? (
        products.map((product) =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{product.id}</span>&gt;</span>{product.name}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>)
      ) : (
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Loading...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      )}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}
</code></pre>
<p>In the code above, when the <code>Home</code> component mounts, it initializes the state as an empty array. The <code>useEffect</code> hook then runs once to make an API request. While the request is in progress, the “Loading...” message is displayed on the screen. Once the request completes successfully, React updates the state with the fetched data and re-renders the UI to display the products. This process introduces an extra round trip, which further delays the FCP.</p>
<h2 id="heading-how-to-reduce-round-trip-time-with-nextjs">How to Reduce Round Trip Time with Next.js</h2>
<p>You cannot eliminate round trip time completely. Data must still travel over the network. What Next.js does is to reduce how often those network requests happen and how much data each request carries. It does this through a number of techniques, such as <strong>Server-Side Rendering (SSR)</strong>, <strong>React Server Components (RSC)</strong>, <strong>image optimization</strong>, and <strong>caching or static rendering</strong>.</p>
<h3 id="heading-server-side-rendering-ssr">Server-Side Rendering (SSR)</h3>
<p>Unlike traditional React.js applications, where the browser handles the majority of the work, such as fetching static files, JavaScript, and data required to render a page, the server generates the whole HTML, fetches the data, renders the page, and sends it to the browser in a single round trip time.</p>
<p><strong>Advantages:</strong></p>
<ul>
<li><p><strong>Fewer Round Trips:</strong> Since data fetching and rendering take place on the server, the browser receives a ready-to-display page in one round trip time.</p>
</li>
<li><p><strong>Improved First Contentful Paint:</strong> Low round trip time means content displays on the page almost immediately.</p>
</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-comment">// pages/index.js (SSR)</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getServerSideProps</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Fetch data on the server</span>
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://api.example.com/products'</span>);
  <span class="hljs-keyword">const</span> products = <span class="hljs-keyword">await</span> res.json();

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: { products }, <span class="hljs-comment">// Pass data to the page</span>
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params">{ products }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Products<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      {products.map((product) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{product.id}</span>&gt;</span>{product.name}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>In the code above, <code>getServerSideProps</code> runs entirely on the server. When a user visits or refreshes the page, <code>getServerSideProps()</code> is called to fetch product data from the external API. The fetched data is then pre-rendered on the server, meaning the list of products is included in the HTML before it is sent to the browser to display. This removes the additional round trip seen in CSR and improves the FCP, since users see meaningful content as soon as the page loads.</p>
<h3 id="heading-react-server-components-rsc">React Server Components (RSC)</h3>
<p>Server-Side Rendering is a technique where the whole page gets generated on the server. But imagine if only some portions of a page are to be rendered on the server while others are to be rendered on the client?  </p>
<p>React Server Components allow partition of rendering between the server and the client.  </p>
<p>For example, a <code>ProductList</code> component can be rendered on the server, while a <code>SearchInput</code> component renders on the client to manage user interactions.  </p>
<p><strong>Advantages:</strong><br>RSC reduces the overall round trip time (RTT) and also increases the page first contentful paint.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// app/ProductList.jsx (Server Component)</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ProductList</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Fetch data on the server</span>
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://api.example.com/products'</span>);
  <span class="hljs-keyword">const</span> products = <span class="hljs-keyword">await</span> res.json();

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Products<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      {products.map((product) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{product.id}</span>&gt;</span>{product.name}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      ))}
      <span class="hljs-tag">&lt;<span class="hljs-name">ClientSearch</span> /&gt;</span> {/* Client Component */}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ProductList;
</code></pre>
<p>In the code above, <code>ProductList</code> is a server component with a <code>ClientSeacrch</code> component as a child component. The <code>ClientSearch</code> renders in the browser while the rest of the <code>ProductList</code> renders on the server. When the page loads, the server runs <code>fetch()</code> to retrieve product data and renders the complete HTML for the product list on the server while <code>ClientSearch</code> renders on the client side to handle user interactions.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// components/ClientSearch.jsx (Client Component)</span>
<span class="hljs-string">'use client'</span>;

<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ClientSearch</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [query, setQuery] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">input</span>
      <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
      <span class="hljs-attr">value</span>=<span class="hljs-string">{query}</span>
      <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setQuery(e.target.value)}
      placeholder="Search products..."
    /&gt;</span>
  );
}
</code></pre>
<p>The <code>ClientSearch</code> component above handles user interactions, such as updating the search input with <code>useState</code>. It’s marked with <code>'use client'</code>, so it runs entirely in the client side.</p>
<h3 id="heading-image-optimization">Image Optimization</h3>
<p>Images negatively impact round-trip time RTT when they are unoptimized, as larger files take a longer time to transfer from the server to the browser.</p>
<p>Next.js Image component optimizes images automatically:</p>
<p><strong>Resizing</strong>: It adjusts image size based on the user’s device.</p>
<p><strong>Compression</strong>: It uses new formats like WebP to shrink the file size significantly.</p>
<p><strong>Lazy Loading</strong>: Loads images only when they enter the user’s viewport, which reduces the number of initial requests.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// pages/index.js</span>
<span class="hljs-keyword">import</span> Image <span class="hljs-keyword">from</span> <span class="hljs-string">'next/image'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Welcome to Our Store<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Image</span>
        <span class="hljs-attr">src</span>=<span class="hljs-string">"/product.jpg"</span>
        <span class="hljs-attr">alt</span>=<span class="hljs-string">"Product Image"</span>
        <span class="hljs-attr">width</span>=<span class="hljs-string">{500}</span>
        <span class="hljs-attr">height</span>=<span class="hljs-string">{300}</span>
      /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>In the code above, the page uses Next.js built-in <code>Image</code> component to render an optimized image. When the page loads, Next.js optimizes the image resizing, lazy loading, and so on. This means the browser will only download the right image size for the device.</p>
<h3 id="heading-caching-and-static-rendering">Caching and Static Rendering</h3>
<p>With SSR and Server Component, round trip time can still remain high if the server has to process data on every request. Next.js solves this problem with Static Site Generation (SSG) and Incremental Static Regeneration (ISR).  </p>
<p>Here’s how it works:  </p>
<p><strong>Static Site Generation</strong>: Pages are pre-rendered during build time, cached and delivered as static HTML from a CDN.  </p>
<p><strong>Incremental Static Regeneration</strong>: Pages are pre-rendered but can be re-generated in the background after an interval, for example, every 60 seconds.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// app/page.jsx</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> revalidate = <span class="hljs-number">60</span>; <span class="hljs-comment">// Regenerate the page every 60 seconds</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Fetch data on the server</span>
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https://api.example.com/products"</span>, {
    <span class="hljs-attr">next</span>: { <span class="hljs-attr">revalidate</span>: <span class="hljs-number">60</span> }, 
  });
  <span class="hljs-keyword">const</span> products = <span class="hljs-keyword">await</span> res.json();

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Products<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      {products.map((product) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{product.id}</span>&gt;</span>{product.name}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>In the code above, the page uses Incremental Static Regeneration (ISR). The <code>revalidate = 60</code> option is to regenerate the page every 60 seconds. When a user visits the page, the server serves the pre-rendered HTML instantly. The <code>next: { revalidate: 60 }</code> inside the <code>fetch()</code> means that data is cached for 60 seconds. After 60 seconds, the next request will trigger the server to regenerate a fresh data.</p>
<h2 id="heading-trade-offs-across-nextjs-rendering-methods">Trade-offs Across Next.js Rendering Methods</h2>
<p>With <strong>Server-Side Rendering (SSR)</strong>, the browser gets the complete rendered page in only one round round trip. On the other hand, this can also lead to increased server load and a high TTFB. The TTFB (Time to First Byte) is the duration it takes for a user to see the content displayed on their browser.</p>
<p>With <strong>Incremental Static Regeneration (ISR)</strong>, the page is pre-rendered and cached, thus getting an instantaneous response from the server. The page will be re-generated depending on a fixed period (such as every 60 seconds). The downside of this method is that users might see old content before it gets updated.</p>
<p>In <strong>Server Components</strong>, rendering takes place on the server, and only interactive parts are managed on the client. With this, server-side rendering are still maintained while still allowing client interactions. The only drawback is that developers need to be very particular while deciding what to run on the server and what to run on the client.</p>
<h2 id="heading-when-to-use-each-rendering-method">When to Use Each Rendering Method</h2>
<p><strong>Server-Side Rendering (SSR)</strong> should be applied to pages that updates frequently, such as dashboards, user profiles, and so on. SSR guarantee users to always see the latest data.  </p>
<p>As for <strong>Incremental Static Regeneration (ISR)</strong>, it should be applied to pages with infrequent changes, for instance, product listings, marketing pages, or blogs.  </p>
<p>Use <strong>Server Components</strong> when you want part of the page to render on the server while some sections run on the client. For instance, pages that need user interaction like search inputs or filters, while data fetching and rendering takes place on the server.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Round Trip Time (RTT) is one of the hidden factors behind slow page loads. Each network request adds a round trip, and these network delays build up as the browser fetches several resources like scripts, images, and data files. Next.js deals with this issue by minimizing the number of network requests that need to be done before the first content paint.</p>
<ul>
<li><p><strong>Server-Side Rendering (SSR)</strong> and <strong>React Server Components (RSC)</strong> shift data fetching and rendering to the server, which reduces client-side requests.</p>
</li>
<li><p><strong>Image optimization</strong> reduces image size and uses CDNs to deliver content faster from nearby servers.</p>
</li>
<li><p><strong>Caching and static rendering</strong> serve pre-generated pages instantly without further processing from the server.</p>
</li>
</ul>
<p>With these techniques, you can build web applications that load faster and feel more responsive, even for users who are far from your origin server or are on slower networks.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Do Global Execution Context and Temporal Dead Zone Work in JavaScript? ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever wondered how JavaScript runs your code behind the scenes, and how the Global Execution Context actually works? How does hoisting work for var, let, and const? Consider the code bellow: console.log('My age is', age) console.log('My name ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/global-execution-context-and-temporal-dead-zone-explained/</link>
                <guid isPermaLink="false">690b5691972e7bfdc1ed09c1</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Shejan Mahamud ]]>
                </dc:creator>
                <pubDate>Wed, 05 Nov 2025 13:52:17 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762347165225/c7bd75a9-a819-41b6-8a35-4feecfb7cf58.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever wondered how JavaScript runs your code behind the scenes, and how the Global Execution Context actually works? How does hoisting work for <code>var</code>, <code>let</code>, and <code>const</code>?</p>
<p>Consider the code bellow:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">'My age is'</span>, age)
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'My name is'</span>, name)
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'My country is'</span>, country)

<span class="hljs-keyword">var</span> age = <span class="hljs-number">24</span>
<span class="hljs-keyword">let</span> name = <span class="hljs-string">'Shejan'</span>
<span class="hljs-keyword">const</span> country = <span class="hljs-string">'Bangladesh'</span>
sayHi()
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sayHi</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Hi!'</span>)
}
</code></pre>
<p>What do you think the output of this code will be?</p>
<p>The first line will probably print <code>undefined</code>, right? But what about the second line with <code>name</code>? This will throw a “<code>ReferenceError</code>: Cannot access <code>name</code> before initialization.” Why? Because <code>let</code> variables are hoisted but remain uninitialized in the temporal dead zone (TDZ) until their declaration line is reached.</p>
<p>The third line with <code>country</code> will never execute because the code stops at line 2 due to the <code>ReferenceError</code>. But if line 2 wasn’t there, line 3 would throw the same error for the same reason – <code>const</code> also stays in the TDZ.</p>
<p>And what about the <code>sayHi()</code> function call? If we could reach it, it would work perfectly and print "Hi!" because function declarations are fully hoisted with their complete body.</p>
<p>The main question: why and how is all this happening? Let's dive in and find answers to these questions.</p>
<h2 id="heading-heres-what-well-cover">Here’s What We’ll Cover:</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-does-the-global-execution-context-work">How Does the Global Execution Context Work?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-memory-creation-phase">Memory Creation Phase</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-flowchart">Understanding the Flowchart</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-exactly-is-hoisting">What Exactly is Hoisting?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-does-only-var-get-hoisted">Does Only var Get Hoisted?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-temporal-dead-zone-tdz-what-is-it-really">Temporal Dead Zone (TDZ) – What is it Really?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-function-hoisting-the-most-interesting-part">Function Hoisting – The Most Interesting Part!</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-happens-in-the-memory-phase">What Happens in the Memory Phase:</a></p>
</li>
</ul>
<h2 id="heading-how-does-the-global-execution-context-work">How Does the Global Execution Context Work?</h2>
<p>When we run any JavaScript code, the very first thing that happens is the creation of a <strong>Global Execution Context (GEC)</strong>. This is the fundamental concept behind JavaScript execution! This global execution context has two important phases:</p>
<ol>
<li><p><strong>Memory creation phase</strong> (memory phase)</p>
</li>
<li><p><strong>Code execution phase</strong> (thread phase)</p>
</li>
</ol>
<p>Let's look at what happens in each phase, one by one.</p>
<h2 id="heading-memory-creation-phase">Memory Creation Phase</h2>
<p>This is the preparation time. During this phase, the JavaScript engine scans through the entire code once (without executing it) and allocates memory for all variables and functions.</p>
<p>But here's where it gets interesting:</p>
<ul>
<li><p><strong>Variables</strong> (var, let, const) are given space in memory.</p>
<ul>
<li><p><code>var</code> is assigned the value <code>undefined</code>.</p>
</li>
<li><p><code>let</code> and <code>const</code> are placed in memory but remain uninitialized.</p>
</li>
<li><p>Functions (function declarations) are stored in memory with their complete code body.</p>
</li>
</ul>
</li>
</ul>
<p>So what happens in the memory phase for our example code?</p>
<pre><code class="lang-bash">age: undefined
name: &lt;uninitialized&gt;
country: &lt;uninitialized&gt;
sayHi: <span class="hljs-function"><span class="hljs-title">function</span></span>() { console.log(<span class="hljs-string">"Hi!"</span>); }
</code></pre>
<p>As you can see, even before a single line of code is executed, everything is already in memory! This entire process of lifting variables and functions into memory during the memory creation phase is called <strong>Hoisting</strong> – and this is what makes JavaScript execution seem "magical."</p>
<h3 id="heading-code-execution-phase">Code Execution Phase</h3>
<p>Now the real action begins! The JavaScript engine starts executing the code line by line.</p>
<p><strong>Line 1:</strong> <code>console.log("My age is", age);</code></p>
<ul>
<li><p>Looks for <code>age</code> in memory</p>
</li>
<li><p>Finds <code>undefined</code></p>
</li>
<li><p><strong>Output:</strong> <code>My age is undefined</code></p>
</li>
</ul>
<p>Line 2: <code>console.log("My name is", name);</code></p>
<ul>
<li><p>Looks for name in memory Finds that it exists in memory but hasn't been initialized yet (it's in the temporal dead zone, or TDZ – we'll explore this concept in detail later).</p>
</li>
<li><p>Output: <code>ReferenceError</code>: Cannot access <code>name</code> before initialization.</p>
</li>
</ul>
<p>The code execution stops right here! The remaining lines won't be executed.</p>
<p>But what would happen if Lines 2 and 3 weren't there?</p>
<p><strong>Line 4:</strong> <code>var age = 24;</code></p>
<ul>
<li>The value of <code>age</code> in memory gets updated from <code>undefined</code> to <code>24</code></li>
</ul>
<p><strong>Line 5:</strong> <code>let name = "Shejan";</code></p>
<ul>
<li><p><code>name</code> is now initialized with the value <code>"Shejan"</code></p>
</li>
<li><p>From this point forward, <code>name</code> can be accessed</p>
</li>
</ul>
<p><strong>Line 6:</strong> <code>const country = "Bangladesh";</code></p>
<ul>
<li><code>country</code> is initialized with the value <code>"Bangladesh"</code></li>
</ul>
<p><strong>Lines 7-9:</strong> Function call</p>
<ul>
<li><p>The <code>sayHi()</code> function was already loaded with its complete body during the memory phase.</p>
</li>
<li><p>When <code>sayHi()</code> is called, the JavaScript engine creates a new execution context specifically for this function.</p>
</li>
<li><p>This new context is known as a <strong>Function Execution Context (FEC)</strong> – it works as a child of the global execution context.</p>
</li>
</ul>
<p>This function execution context also has <strong>two phases</strong>, just like the global execution context:</p>
<ol>
<li><p><strong>Memory Creation Phase:</strong></p>
<ul>
<li><p>All variables, parameters, and nested functions inside the function are allocated in memory.</p>
</li>
<li><p>Function arguments are assigned.</p>
</li>
<li><p>A function scope is created and a reference link is established with the outer lexical environment (where the function was defined) – this link is called the <strong>scope chain</strong>. The scope chain is JavaScript's way of resolving variable names. It's like a chain of connected scopes. When JavaScript looks for a variable inside a function, it first checks the function's own scope. If it doesn't find the variable there, it moves up the chain to check the parent scope (where the function was defined), then the grandparent scope, and so on, until it reaches the global scope. This chain ensures that functions can access variables from their outer environments.</p>
</li>
</ul>
</li>
<li><p><strong>Code Execution (Thread) Phase:</strong></p>
<ul>
<li><p>Now the function body is executed line by line.</p>
</li>
<li><p><code>console.log("Hi!");</code> executes and prints <strong>"Hi!"</strong></p>
</li>
</ul>
</li>
</ol>
<p>Once the function execution is complete:</p>
<ul>
<li><p>That function execution context is popped off the call stack.</p>
</li>
<li><p>And control returns to the global execution context.</p>
</li>
</ul>
<p><strong>Note</strong>: When all code execution is complete, the global execution context is also popped off the call stack.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761988425883/33792916-eee7-4f87-b365-51a04290aa96.png" alt="Flowchart diagram illustrating JavaScript Global Execution Context workflow, showing the two phases - Memory Creation Phase where variables and functions are allocated, and Code Execution Phase where code runs line by line, including how Function Execution Context is created when functions are called" class="image--center mx-auto" width="2724" height="2715" loading="lazy"></p>
<p>The flowchart diagram above illustrates the JavaScript global execution context workflow, showing the two phases. In the memory creation phase, variables and functions are allocated, and in the code execution phase, the code runs line by line. It also shows how the function execution context is created when functions are called.</p>
<h2 id="heading-understanding-the-flowchart">Understanding the Flowchart</h2>
<p>The diagram above visualizes the complete journey of JavaScript code execution from start to finish.</p>
<p><strong>The flow begins</strong> when JavaScript execution starts and immediately creates a global execution context (GEC). This context then splits into two distinct phases, shown as a diamond in the diagram.</p>
<p><strong>On the left side - Memory Creation Phase:</strong> You can see three parallel branches showing how different types of declarations are handled:</p>
<ul>
<li><p><code>var</code> variables are allocated with the value <code>undefined</code></p>
</li>
<li><p><code>let</code> and <code>const</code> variables are allocated but remain uninitialized (in the temporal dead zone)</p>
</li>
<li><p>Function declarations are fully hoisted with their complete body</p>
</li>
</ul>
<p><strong>On the right side - Code Execution Phase:</strong> JavaScript now executes the code line by line. During execution:</p>
<ul>
<li><p>It accesses variable values from memory</p>
</li>
<li><p>If you try to access <code>let</code> or <code>const</code> before initialization, you get a ReferenceError (temporal dead zone)</p>
</li>
<li><p>If you access <code>var</code> before assignment, you get <code>undefined</code></p>
</li>
<li><p>When a function is called, a new Function Execution Context (FEC) is created</p>
</li>
</ul>
<p><strong>The Function Execution Context (FEC)</strong> (shown in the right branch) works as a child of the GEC and has its own Memory and Execution phases. After the function executes completely, the FEC is popped off the call stack and control returns to the GEC.</p>
<p><strong>Finally</strong>, when all code execution is complete, the GEC itself is popped from the call stack, and the program ends.</p>
<p>This visual representation helps you understand that JavaScript doesn't just read and run your code - it prepares everything first (Memory Phase) and then executes it systematically (Execution Phase).</p>
<h2 id="heading-what-exactly-is-hoisting">What Exactly is Hoisting?</h2>
<p>Hoisting is JavaScript's default behavior of moving variable and function declarations to memory before code execution begins.</p>
<p>Think of it this way – it appears as if all declarations automatically move to the very top of the code. Although the code doesn't physically move, the memory allocation happens first.</p>
<h2 id="heading-does-only-var-get-hoisted">Does Only <code>var</code> Get Hoisted?</h2>
<p>This surprises many people, but the answer is no. It's not just <code>var</code> that gets hoisted! This is a huge misconception among many developers.</p>
<p>The truth is that—<code>let</code>, <code>const</code>, and functions—everything gets hoisted! But their behavior is completely different. Let's dive into the details.</p>
<h3 id="heading-what-happens-with-var">What Happens with <code>var</code>?</h3>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(name) <span class="hljs-comment">// undefined</span>
<span class="hljs-keyword">var</span> name = <span class="hljs-string">'Rahim'</span>
<span class="hljs-built_in">console</span>.log(name) <span class="hljs-comment">// "Rahim"</span>
</code></pre>
<p>Variables declared with <code>var</code>:</p>
<ul>
<li><p>Get hoisted</p>
</li>
<li><p>Are initialized with <code>undefined</code></p>
</li>
<li><p>Exist in global scope or function scope</p>
</li>
<li><p>Can be accessed before declaration (doesn't throw an error)</p>
</li>
</ul>
<h3 id="heading-what-happens-with-let">What Happens with <code>let</code>?</h3>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(name) <span class="hljs-comment">// ReferenceError: Cannot access 'name' before initialization</span>
<span class="hljs-keyword">let</span> name = <span class="hljs-string">'Rahim'</span>
<span class="hljs-built_in">console</span>.log(name) <span class="hljs-comment">// "Rahim"</span>
</code></pre>
<p>Is this magic? No! Actually, <code>let</code> does get hoisted, but it's stuck in a special state called the temporal dead zone!</p>
<p>Space is allocated in memory, but it's not initialized. So if you try to access it before declaration, JavaScript says – "Hey, the variable exists, but you can't use it yet!"</p>
<h3 id="heading-what-happens-with-const">What Happens with <code>const</code>?</h3>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(age) <span class="hljs-comment">// ReferenceError: Cannot access 'age' before initialization</span>
<span class="hljs-keyword">const</span> age = <span class="hljs-number">24</span>
<span class="hljs-built_in">console</span>.log(age) <span class="hljs-comment">// 24</span>
</code></pre>
<p>The behavior of <code>const</code> is exactly the same as <code>let</code>:</p>
<ul>
<li><p>Gets hoisted</p>
</li>
<li><p>Stays in the TDZ until declaration</p>
</li>
<li><p>Exists in block scope</p>
</li>
<li><p>Plus, once assigned, it cannot be reassigned</p>
</li>
</ul>
<h2 id="heading-temporal-dead-zone-tdz-what-is-it-really">Temporal Dead Zone (TDZ) – What is it Really?</h2>
<p>The Temporal Dead Zone is the time period or zone when a variable exists in memory (due to hoisting) but hasn't been initialized yet. During this time, the variable is essentially "dead" – meaning that you cannot access it.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ← TDZ starts for x and y</span>
<span class="hljs-built_in">console</span>.log(x) <span class="hljs-comment">// ReferenceError - still in TDZ</span>
<span class="hljs-built_in">console</span>.log(y) <span class="hljs-comment">// ReferenceError - still in TDZ</span>

<span class="hljs-comment">// TDZ continues...</span>
<span class="hljs-keyword">let</span> x = <span class="hljs-number">10</span> <span class="hljs-comment">// ← x's TDZ ends at this line</span>
<span class="hljs-keyword">const</span> y = <span class="hljs-number">20</span> <span class="hljs-comment">// ← y's TDZ ends at this line</span>

<span class="hljs-built_in">console</span>.log(x) <span class="hljs-comment">// 10 - can access now</span>
<span class="hljs-built_in">console</span>.log(y) <span class="hljs-comment">// 20 - can access now</span>
</code></pre>
<p>The entire concept of TDZ is to force us to write better code. Using variables before declaring them is a bad practice, and TDZ prevents us from doing that.</p>
<h2 id="heading-function-hoisting-the-most-interesting-part">Function Hoisting – The Most Interesting Part!</h2>
<p>Hoisting with functions is even more interesting and powerful:</p>
<pre><code class="lang-javascript">greet() <span class="hljs-comment">// "Hello World!" - Perfect! It works!</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">greet</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Hello World!'</span>)
}
</code></pre>
<p>How is this possible? Because function declarations are completely hoisted! This means not just the name, but the entire function body is lifted into memory. That's why it can be called even before the declaration.</p>
<p>But wait! Not all functions work this way.</p>
<h3 id="heading-with-function-expressions">With Function Expressions:</h3>
<pre><code class="lang-javascript">greet() <span class="hljs-comment">// TypeError: greet is not a function</span>

<span class="hljs-keyword">var</span> greet = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Hello World!'</span>)
}
</code></pre>
<p>What happened here? <code>greet</code> was hoisted as a variable and received the value <code>undefined</code>. It wasn't hoisted as a function! So when you try to call it, you get an error. In other words, it's hoisted as a variable (assigned <code>undefined</code>), but the function body isn't loaded into memory.</p>
<h3 id="heading-with-arrow-functions">With Arrow Functions:</h3>
<pre><code class="lang-javascript">sayHello() <span class="hljs-comment">// ReferenceError (if let/const is used)</span>

<span class="hljs-keyword">const</span> sayHello = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Hello!'</span>)
}
</code></pre>
<p>Arrow functions behave just like function expressions. They follow variable rules.</p>
<p>Let's clear everything up with a complete example:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(a) <span class="hljs-comment">// undefined (var hoisting)</span>
<span class="hljs-built_in">console</span>.log(b) <span class="hljs-comment">// ReferenceError (TDZ)</span>
<span class="hljs-built_in">console</span>.log(c) <span class="hljs-comment">// ReferenceError (TDZ)</span>
multiply(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>) <span class="hljs-comment">// 6 (function hoisting)</span>
add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>) <span class="hljs-comment">// TypeError (function expression)</span>

<span class="hljs-keyword">var</span> a = <span class="hljs-number">10</span>
<span class="hljs-keyword">let</span> b = <span class="hljs-number">20</span>
<span class="hljs-keyword">const</span> c = <span class="hljs-number">30</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">multiply</span>(<span class="hljs-params">x, y</span>) </span>{
  <span class="hljs-keyword">return</span> x * y
}

<span class="hljs-keyword">var</span> add = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">x, y</span>) </span>{
  <span class="hljs-keyword">return</span> x + y
}
</code></pre>
<h2 id="heading-what-happens-in-the-memory-phase">What Happens in the Memory Phase:</h2>
<pre><code class="lang-javascript">a: <span class="hljs-literal">undefined</span>
<span class="hljs-attr">b</span>: &lt;uninitialized&gt;
c: &lt;uninitialized&gt;
multiply: function(x, y) { return x * y; }
add: undefined
</code></pre>
<p>This snapshot represents the state of memory before any code is executed. Here's what each line means:</p>
<p><code>a: undefined</code> - Since <code>a</code> is declared with <code>var</code>, it gets hoisted and immediately assigned the value <code>undefined</code>. This is why you get <code>undefined</code> instead of an error when you try to access <code>a</code> before its declaration line.</p>
<p><code>b: &lt;uninitialized&gt;</code> - The variable <code>b</code> is declared with <code>let</code>, so it's hoisted and memory is allocated for it, but it remains uninitialized. It's in the Temporal Dead Zone (TDZ). Attempting to access it before the declaration line will throw a <code>ReferenceError</code>.</p>
<p><code>c: &lt;uninitialized&gt;</code> - Similarly, <code>c</code> is declared with <code>const</code> and follows the same behavior as <code>let</code>. It's hoisted but stays uninitialized in the TDZ until the declaration line is reached.</p>
<p><code>multiply: function(x, y) { return x * y; }</code> - This is a function declaration, so it's fully hoisted with its complete body. The entire function is stored in memory and ready to be called even before the JavaScript engine reaches its declaration in the code. This is why <code>multiply(2, 3)</code> works perfectly and returns <code>6</code>.</p>
<p><code>add: undefined</code> - Here's the crucial difference! Even though <code>add</code> will eventually store a function, it's declared using <code>var add = function() {...}</code> (a function expression). During the memory phase, only the variable <code>add</code> is hoisted and initialized with <code>undefined</code>. The actual function body isn't assigned until the execution phase reaches line 11. This is why calling <code>add(2, 3)</code> before the assignment throws a <code>TypeError: add is not a function</code> - you're essentially trying to execute <code>undefined()</code>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Understanding JavaScript's execution mechanism is fundamental to becoming a proficient developer. Let's recap the essential concepts we've explored:</p>
<p><strong>The Global Execution Context (GEC)</strong> is the foundation of JavaScript execution. Every time you run JavaScript code, the GEC is created first. It works in two critical phases:</p>
<ul>
<li><p><strong>Memory Creation Phase</strong>: JavaScript prepares by scanning the code and allocating memory for variables and functions.</p>
</li>
<li><p><strong>Code Execution Phase</strong>: JavaScript runs your code line by line.</p>
</li>
</ul>
<p><strong>Hoisting is universal</strong> - not just limited to <code>var</code>. Here's how different declarations are hoisted:</p>
<ul>
<li><p><code>var</code> variables are hoisted and initialized with <code>undefined</code>.</p>
</li>
<li><p><code>let</code> and <code>const</code> are hoisted but remain uninitialized in the TDZ.</p>
</li>
<li><p>Function declarations are fully hoisted with their entire body.</p>
</li>
<li><p>Function expressions and arrow functions follow variable hoisting rules</p>
</li>
</ul>
<p><strong>The Temporal Dead Zone (TDZ)</strong> is JavaScript's built-in safety mechanism. It exists from the start of the scope until the variable declaration line is reached. The TDZ prevents us from accessing <code>let</code> and <code>const</code> variables before they're declared, encouraging better coding practices and helping us avoid bugs.</p>
<p><strong>Function hoisting behavior varies</strong>:</p>
<ul>
<li><p>Function declarations can be called before they appear in code.</p>
</li>
<li><p>Function expressions behave like variables and cannot be called before assignment.</p>
</li>
<li><p>Arrow functions follow the same rules as function expressions.</p>
</li>
</ul>
<p><strong>Why does this matter?</strong> Understanding these concepts helps you:</p>
<ul>
<li><p>Predict how your code will behave before running it.</p>
</li>
<li><p>Avoid common errors like <code>ReferenceError</code> and <code>TypeError</code>.</p>
</li>
<li><p>Write cleaner, more maintainable code.</p>
</li>
<li><p>Debug issues faster when they arise.</p>
</li>
<li><p>Make informed decisions about when to use <code>var</code>, <code>let</code>, or <code>const</code>.</p>
</li>
</ul>
<p><strong>The key takeaway</strong>: <strong>JavaScript doesn't just execute your code - it prepares first, then executes.</strong> The memory phase sets up the stage, and the execution phase performs the show. Master this two-phase process, and you'll have a solid understanding of how JavaScript works under the hood.</p>
<p>Now you're equipped with the knowledge to write better JavaScript code and understand exactly what's happening behind the scenes. Keep practicing these concepts, and they'll become second nature!</p>
<p>Happy Coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Optimize a Graphical React Codebase — Optimize d3-zoom and dnd-kit Code ]]>
                </title>
                <description>
                    <![CDATA[ Miro and Figma are online collaborative canvas tools that became very popular during the pandemic. Instead of using sticky notes on a physical wall, you can add a virtual post—and an array of other things—to a virtual canvas. This lets teams collabor... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-optimize-a-graphical-react-codebase/</link>
                <guid isPermaLink="false">68f0e6e94d664bb368c83f39</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Cedd Burge ]]>
                </dc:creator>
                <pubDate>Thu, 16 Oct 2025 12:36:57 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760617553059/99fd830f-39a8-4067-9727-e9b35850168d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Miro and Figma are online collaborative canvas tools that became very popular during the pandemic. Instead of using sticky notes on a physical wall, you can add a virtual post—and an array of other things—to a virtual canvas. This lets teams collaborate virtually in ways that feel familiar from the physical world.</p>
<p>I previously wrote an article showing <a target="_blank" href="https://www.freecodecamp.org/news/how-to-create-a-figma-miro-style-canvas-with-react-and-typescript/">how to create a Figma/Miro Clone in React and TypeScript</a>. The <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-part-3">code in the article</a> was designed to be as easy to understand, and in this article, we’re going to optimize it. The code used <a target="_blank" href="https://dndkit.com/">DndKit</a> for dragging and dropping, and <a target="_blank" href="https://github.com/d3/d3-zoom">D3 Zoom</a> for panning and zooming. There were four components (<code>App</code>, <code>Canvas</code>, <code>Draggable</code> and <code>Addable</code>), and about 250 lines of code. You do not need to read the original article to understand this one.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759863968693/38c8784c-47c8-46e0-9f06-0567c0ebf668.gif" alt="38c8784c-47c8-46e0-9f06-0567c0ebf668" class="image--center mx-auto" width="1026" height="730" loading="lazy"></p>
<p>Standard optimizations such as <code>useCallback</code>, <code>memo</code>, and similar made it about twice as fast when dragging, but made no difference for panning and zooming. More creative/intensive optimizations made it about ten times as fast in most cases.</p>
<p>You can see the optimized code on <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised">GitHub</a> and there is a <a target="_blank" href="https://ceddlyburge.github.io/react-figma-miro-canvas-optimised/">live demo on GitHub pages</a> to test out the speed with 100,000 cards.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-measure-performance-in-react-apps">How to Measure Performance in React Apps</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-investigate-the-performance">How to Investigate the performance</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-optimize-panning-and-zooming-the-canvas">How to Optimize Panning and Zooming the Canvas</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-optimize-dragging-cards-around-the-canvas">How to Optimize Dragging Cards Around the Canvas</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-results">Results</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-how-to-measure-performance-in-react-apps">How to Measure Performance in React Apps</h2>
<p>There are three common ways to measure performance in React Apps</p>
<ul>
<li><p><a target="_blank" href="https://chromewebstore.google.com/detail/react-developer-tools">React Dev Tools profiler</a></p>
</li>
<li><p><a target="_blank" href="https://developer.chrome.com/docs/devtools/performance/overview">Chrome Dev Tools profiler</a>, especially using <a target="_blank" href="https://www.debugbear.com/blog/favourite-devtools-features-in-2025#add-custom-tracks-to-performance-recordings">custom tracks</a></p>
</li>
<li><p><a target="_blank" href="https://react.dev/reference/react/Profiler">Profiler component</a></p>
</li>
</ul>
<p>These tools are all great, but none of them are quite the right fit in this case. In most codebases, the time spent executing JavaScript code (both our code and that of the React framework) is the primary issue. However, after all your code has run and React has updated the Dom, the browser still has a lot of work to do:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760025146277/7ae9ef2a-e248-491b-a07d-c674694d3fa9.png" alt="7ae9ef2a-e248-491b-a07d-c674694d3fa9" class="image--center mx-auto" width="898" height="219" loading="lazy"></p>
<p>In this case, this browser layout and rendering time was significant, and is not accounted for by the React profiling.</p>
<p>You can use custom tracks in the Chrome dev tools profiler, but it is very cumbersome to use.</p>
<p>For us, the JavaScript <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Performance">performance API</a> is the best option, which gives results that are closer to those experienced by the user, and is relatively easy to use.</p>
<p>First, we make a call to <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark"><code>performance.mark</code></a> in the event handler that starts the action, with a string to describe the time point. For example, when starting a zoom or pan operation:</p>
<pre><code class="lang-javascript">zoomBehavior.on(<span class="hljs-string">"start"</span>, <span class="hljs-function">() =&gt;</span> {
    performance.mark(<span class="hljs-string">'zoomingOrPanningStart'</span>);
}
</code></pre>
<p>Then, in a <code>useEffect</code> hook, we call <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark"><code>performance.mark</code></a> again, and call <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure"><code>performance.measure</code></a> to calculate the time between the two points:</p>
<pre><code class="lang-javascript">useEffect(<span class="hljs-function">() =&gt;</span> {
    performance.mark(<span class="hljs-string">'zoomingOrPanningEnd'</span>);
    performance.measure(<span class="hljs-string">'zoomingOrPanning'</span>, <span class="hljs-string">'zoomingOrPanningStart'</span>, <span class="hljs-string">'zoomingOrPanningEnd'</span>);
});
</code></pre>
<p>The <a target="_blank" href="https://react.dev/reference/react/useEffect">React docs</a> states that <code>useEffect</code> usually fires after the browser has painted the updated screen, which is what we want.</p>
<p>This isn't perfect, and will vary depending on the machine specifications, and what else the machine is doing at the time, but it was good enough to verify which optimizations worked best. It is possible to go further if you need to. For example, using <a target="_blank" href="https://filiphric.com/testing-frontend-performance-with-cypress">Cypress to automate and profile scenarios</a>, potentially running many times to get a good mean, or using <a target="_blank" href="https://www.browserstack.com/guide/performance-testing-with-cypress">Browserstack to test on a variety of devices</a>.</p>
<h2 id="heading-how-to-investigate-the-performance">How to Investigate the Performance</h2>
<p>Most of the investigation involved using the <a target="_blank" href="https://chromewebstore.google.com/detail/react-developer-tools">React Dev Tools profiler</a> to record profiles of user interactions.</p>
<p>The performance data shows how many commits there were in the profile, and how long each one took, which is a great way to see if there are too many commits.</p>
<p>Each commit displays a flame chart showing which components rendered and why they re-rendered. This makes it much easier to find ways to avoid the re-rendering, and to check that memoization strategies are working as expected. This does have some caveats though. It often says <a target="_blank" href="https://github.com/facebook/react/issues/19732">'The parent component rendered'</a>, which is misleading default text for when it doesn’t understand what happened (and is often due to a change in a parent context). It also says things like 'hook 9 changed', which makes it time consuming to work out exactly which hook changed.</p>
<p>The flame chart also shows how long each component took to render. This helps target problem components that we need to focus on.</p>
<h2 id="heading-how-to-optimize-panning-and-zooming-the-canvas">How to Optimize Panning and Zooming the Canvas</h2>
<p>The original <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-part-3/blob/main/src/Canvas.tsx">Canvas</a> element used the CSS transform <code>translate3d(x, y, k)</code> to pan and zoom the canvas. This works, but it doesn't scale child elements, so when the zoom changes, all the cards on the canvas have to be re-rendered with a new CSS transform for the new zoom level (<a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-part-3/blob/97935f5b03ecff2f0732f6138e173a0c5e71a1ed/src/Draggable.tsx#L31"><code>scale(${canvasTransform.k})</code></a>).</p>
<pre><code class="lang-javascript"> &lt;div
    ...
    className=<span class="hljs-string">"canvas"</span>
    style={{
        <span class="hljs-attr">transform</span>: <span class="hljs-string">`translate3d(<span class="hljs-subst">${transform.x}</span>px, <span class="hljs-subst">${transform.y}</span>px, <span class="hljs-subst">${transform.k}</span>px)`</span>,
        ...
    }}&gt;
    ...
&lt;/div&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>
    <span class="hljs-attr">className</span>=<span class="hljs-string">"card"</span>
    <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">...</span>
        <span class="hljs-attr">transform:</span> `<span class="hljs-attr">scale</span>(${<span class="hljs-attr">canvasTransform.k</span>})`,
    }}&gt;</span>
    ...
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
</code></pre>
<p>I changed this to use <code>translateX(x) translateY(y) scale(k)</code>, which has the same effect, but does scale child elements. This way, when the zoom changes, none of the cards will be re-rendered (the <code>style</code> of the <code>card</code> component no longer uses the <code>canvasTransform.k</code>).</p>
<pre><code class="lang-javascript"> &lt;div
    ...
    className=<span class="hljs-string">"canvas"</span>
    style={{
        <span class="hljs-attr">transform</span>: <span class="hljs-string">`translateX(<span class="hljs-subst">${transform.x}</span>px) translateY(<span class="hljs-subst">${transform.y}</span>px) scale(<span class="hljs-subst">${transform.k}</span>)`</span>,
        ...
    }}&gt;
    ...
&lt;/div&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>
    <span class="hljs-attr">className</span>=<span class="hljs-string">"card"</span>
    <span class="hljs-attr">...</span>
&lt;/<span class="hljs-attr">div</span>&gt;</span></span>
</code></pre>
<p>The <code>Canvas</code> still needed to re-render whenever the pan or zoom changed, and it is possible to prevent this with <code>useRef</code>, and updating the CSS transform with direct JavaScript Dom manipulation in the <a target="_blank" href="https://d3js.org/d3-zoom">d3-zoom</a> event handler. This doesn’t make a significant improvement to the performance though, and is a definite hack, so the trade off is not worthwhile.</p>
<p>Both zooming and panning get a bit slower when the canvas is zoomed very far out and there are (a lot) more cards visible on the screen, just due to the browser having to render them all. It's still workable at 100,000 cards though. There are things you can do about this. An easy option is limiting the maximum zoom extent. This is a functional change, so potentially something that doesn’t meet requirements, but it is easy to do in d3-zoom using <a target="_blank" href="https://d3js.org/d3-zoom#zoom_scaleExtent"><code>scaleExtent</code></a>:</p>
<pre><code class="lang-javascript">zoom&lt;HTMLDivElement&gt;().scaleExtent([<span class="hljs-number">0.1</span>, <span class="hljs-number">100</span>])
</code></pre>
<p>Another option is to create a bitmap for very low zoom levels and render that as a single element. This may be difficult, but it means that there will be no change to the functionality.</p>
<h2 id="heading-how-to-optimize-dragging-cards-around-the-canvas">How to Optimize Dragging Cards Around the Canvas</h2>
<h3 id="heading-starting-a-drag"><strong>Starting a drag</strong></h3>
<p>The <code>useDraggable</code> hook from <code>DndContext</code> causes some re-renders when starting a drag operation.</p>
<p>It is possible to improve this by changing the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component to just have this hook (and the things that use it) and having a <code>DraggableInner</code> component for everything else (inside a <code>memo</code>). This works well for reducing the re-renders, in that the <code>DraggableInner</code> almost never get re-rendered, and improves the speed of starting a drag operation. However, it was still fairly slow, and the time was all under the <code>DndContext</code>.</p>
<p>A better option is to create a new <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/NonDraggable.tsx"><code>NonDraggable</code></a> component, that looks exactly like the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component, but does not hook up with <code>DndContext</code>. These cards are shown on the Canvas, and have an <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/56d0c8256350ef3a0d8f50cc442305bd6c9d03c1/src/NonDraggable.tsx#L10"><code>onMouseEnter</code></a> event, to swap in the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component for the active card, so that dragging continued to work.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> onMouseEnter = useCallback(<span class="hljs-function">() =&gt;</span> {
    setHoverCard(card);
}, []);
</code></pre>
<p>This works well, and significantly improves the speed when starting a drag operation, but it was still quite slow with large numbers of cards. Nearly nothing was getting re-rendered, but there is still a time cost to when using <code>memo</code>, as it needs to check whether components have changed.</p>
<p>To fix this, we create an <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/AllCards.tsx"><code>AllCards</code></a> component, that contains all the cards on the canvas as <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/NonDraggable.tsx"><code>NonDraggable</code></a> components. Because it always renders all the cards, it nearly never needs to be re-rendered, and it is used with <code>memo</code>. So instead of each individual card using a <code>memo</code> (with the associated time cost), there is now just one component using a <code>memo</code>. To make it so that the dragging still works, the active <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component is rendered on top, obscuring the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/NonDraggable.tsx"><code>NonDraggable</code></a> component beneath it. There is also a <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Cover.tsx"><code>Cover</code></a> component beneath that, so that when the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component is dragged away, the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/NonDraggable.tsx"><code>NonDraggable</code></a> component underneath remains hidden.</p>
<p>Original code, where each card is a <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component:</p>
<pre><code class="lang-javascript">&lt;DndContext ...&gt;
    {cards.map(<span class="hljs-function">(<span class="hljs-params">card</span>) =&gt;</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Draggable</span> <span class="hljs-attr">card</span>=<span class="hljs-string">{card}</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{card.id}</span> <span class="hljs-attr">canvasTransform</span>=<span class="hljs-string">{transform}</span> /&gt;</span></span>
    ))}
&lt;/DndContext&gt;
</code></pre>
<p>Optimized code, where the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/AllCards.tsx"><code>AllCards</code></a> component renders all the cards as <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/NonDraggable.tsx"><code>NonDraggable</code></a> components, and then a <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Cover.tsx"><code>Cover</code></a> and a <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component for the active card.</p>
<pre><code class="lang-javascript">&lt;AllCards cards={cards} setHoverCard={setHoverCard} /&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DndContext</span> <span class="hljs-attr">...</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Cover</span> <span class="hljs-attr">card</span>=<span class="hljs-string">{hoverCard}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Draggable</span> <span class="hljs-attr">card</span>=<span class="hljs-string">{hoverCard}</span> <span class="hljs-attr">canvasTransform</span>=<span class="hljs-string">{transform}</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">DndContext</span>&gt;</span></span>
</code></pre>
<p>This works very well. With a low number of cards, the speed is about the same, but with a high numbers of cards, it’s about twenty times faster.</p>
<p>There is now a new potential performance issue with the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/56d0c8256350ef3a0d8f50cc442305bd6c9d03c1/src/NonDraggable.tsx#L10"><code>onMouseEnter</code></a> event that swaps in the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component for the active card, but this just adds two components to the Dom, and is very quick even with large numbers of cards.</p>
<h3 id="heading-finishing-a-drag"><strong>Finishing a drag</strong></h3>
<p>Finishing a drag operation is hard to optimize, as the position of a card changes, and that does need to re-render, which means that the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/AllCards.tsx"><code>AllCards</code></a> component has to re-render as well.</p>
<p>You can see original code below. Even when using <code>memo</code> with the Draggable component, the end drag operation still takes 2500ms with 100,000 cards, mostly due to the complexity of the <code>Draggable</code> component and its integration with DndKit.</p>
<pre><code class="lang-javascript">&lt;DndContext ...&gt;
    {cards.map(<span class="hljs-function">(<span class="hljs-params">card</span>) =&gt;</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Draggable</span> <span class="hljs-attr">card</span>=<span class="hljs-string">{card}</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{card.id}</span> <span class="hljs-attr">canvasTransform</span>=<span class="hljs-string">{transform}</span> /&gt;</span></span>
    ))}
&lt;/DndContext&gt;
</code></pre>
<p>However, we now use the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/NonDraggable.tsx"><code>NonDraggable</code></a> components, which all <code>memo</code> successfully, and only the dragged card is re-rendered. There is still a time cost using the <code>memo</code>, and this is the slowest part of the solution, but it leads to an increase in speed to 500ms with 100,000 cards.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> NonDraggable = memo(...)

<span class="hljs-keyword">const</span> AllCards = memo(<span class="hljs-function">(<span class="hljs-params">cards, setHoverCard</span>) =&gt;</span> {
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
        {cards.map((card) =&gt; {
            <span class="hljs-tag">&lt;<span class="hljs-name">NonDraggable</span> <span class="hljs-attr">card</span>=<span class="hljs-string">{card}</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{card.id}</span> <span class="hljs-attr">setHoverCard</span>=<span class="hljs-string">{setHoverCard}</span> /&gt;</span>);
        })}
    <span class="hljs-tag">&lt;/&gt;</span></span>;
});
</code></pre>
<h2 id="heading-results"><strong>Results</strong></h2>
<p>The base unoptimized version started to get slow between 1000 and 5000 cards. Standard optimizations improved this to around 10,000 cards, and the more optimization took it to about 100,000 cards. The trade off is that the code becomes significantly more complicated, which makes it harder to understand and modify, especially for people new to the codebase.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td></td><td><strong>Pan (ms)</strong></td><td><strong>Zoom (ms)</strong></td><td><strong>Start drag (ms)</strong></td><td><strong>End drag (ms)</strong></td><td><strong>Card hover (ms)</strong></td></tr>
</thead>
<tbody>
<tr>
<td>1000 cards</td><td>Base</td><td>3</td><td>4</td><td>200</td><td>50</td><td>-</td></tr>
<tr>
<td></td><td>Basic optimization</td><td>2</td><td>3</td><td>200</td><td>30</td><td>-</td></tr>
<tr>
<td></td><td>Intensive optimization</td><td>10</td><td>10</td><td>7</td><td>15</td><td>2</td></tr>
<tr>
<td>5000 cards</td><td>Base</td><td>20</td><td>150</td><td>450</td><td>200</td><td>-</td></tr>
<tr>
<td></td><td>Basic optimization</td><td>20</td><td>150</td><td>200</td><td>80</td><td>-</td></tr>
<tr>
<td></td><td>Intensive optimization</td><td>10</td><td>10</td><td>25</td><td>40</td><td>2</td></tr>
<tr>
<td>10,000 cards</td><td>Base</td><td>50</td><td>300</td><td>900</td><td>400</td><td>-</td></tr>
<tr>
<td></td><td>Basic optimization</td><td>50</td><td>300</td><td>400</td><td>180</td><td>-</td></tr>
<tr>
<td></td><td>Intensive optimization</td><td>25</td><td>25</td><td>50</td><td>50</td><td>2</td></tr>
<tr>
<td>50,000 cards</td><td>Base</td><td>1000</td><td>1500</td><td>4000</td><td>1800</td><td>-</td></tr>
<tr>
<td></td><td>Basic optimization</td><td>1000</td><td>1500</td><td>1900</td><td>900</td><td>-</td></tr>
<tr>
<td></td><td>Intensive optimization</td><td>150</td><td>150</td><td>150</td><td>250</td><td>5</td></tr>
<tr>
<td>100,000 cards</td><td>Base</td><td>-</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
<tr>
<td></td><td>Basic optimization</td><td>3000</td><td>4500</td><td>5000</td><td>2500</td><td>-</td></tr>
<tr>
<td></td><td>Intensive optimization</td><td>150</td><td>250</td><td>300</td><td>500</td><td>15</td></tr>
</tbody>
</table>
</div><h2 id="heading-summary">Summary</h2>
<p>It is unusual to display 100,000 or more items on screen in a standard React App, but in a highly graphical codebase, it becomes much more likely.</p>
<p>With these numbers, the browser rendering engine is likely to take a significant amount of time, so it is best to use the performance API to measure performance, instead of the usual React tools.</p>
<p>Standard React optimization strategies do work and improve the situation, but there is a need to go further, by finding ways to avoid renders, and even to avoid too many <code>memo</code> comparisons.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Fix Memory Leaks in React Applications ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever noticed your React application getting slower the longer you use it? This could be a result of memory leaks. Memory leaks are a common performance issue in React applications. They can slow down your application, crash your browser, and... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/fix-memory-leaks-in-react-apps/</link>
                <guid isPermaLink="false">68d443c623251efd1f372d02</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Olaleye Blessing ]]>
                </dc:creator>
                <pubDate>Wed, 24 Sep 2025 19:17:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758741256644/817dba0f-bf49-424c-9b13-86bf81dc327f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever noticed your React application getting slower the longer you use it? This could be a result of memory leaks. Memory leaks are a common performance issue in React applications. They can slow down your application, crash your browser, and frustrate users.</p>
<p>In this tutorial, you’ll learn what causes memory leaks and how to fix them.</p>
<h2 id="heading-table-of-contents">Table Of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-memory-leaks-in-react">What Are Memory Leaks in React?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-does-a-component-unmount">When Does A Component Unmount?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-common-causes-of-memory-leaks-and-how-to-fix-them">Common Causes Of Memory Leaks And How To Fix Them</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-event-listeners">Event Listeners</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-timers">Timers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-subscriptions">Subscriptions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-async-operations">Async Operations</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you move on, make sure you have:</p>
<ul>
<li><p>Basic knowledge of JavaScript, React, and React hooks</p>
</li>
<li><p>Understanding of event handling, timers, and asynchronous calls</p>
</li>
<li><p>A React development setup.</p>
</li>
</ul>
<p>If you don’t have a React development setup, you can head over to the <a target="_blank" href="https://github.com/Olaleye-Blessing/freecodecamp-fix-memory-leak">memory-leak repo</a>. Run the commands below to set it up:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># clone the repo</span>
git <span class="hljs-built_in">clone</span> &lt;https://github.com/Olaleye-Blessing/freecodecamp-fix-memory-leak.git&gt;

<span class="hljs-comment"># navigate to the folder</span>
<span class="hljs-built_in">cd</span> freecodecamp-fix-memory-leak.git

<span class="hljs-comment"># install the packages</span>
pnpm install

<span class="hljs-comment"># start development</span>
pnpm dev
</code></pre>
<h2 id="heading-what-are-memory-leaks-in-react">What Are Memory Leaks in React?</h2>
<p>In JavaScript, memory leaks happen when an application allocates memory but fails to release it. This occurs even after the memory is no longer needed.</p>
<p>In React, memory leaks happen when a component creates resources but does not remove them when it unmounts. These resources can be event listeners, timers, or subscriptions.</p>
<p>As a user stays longer in the application, these unreleased resources accumulate. This accumulation causes the application to consume more RAM. This will eventually lead to several problems:</p>
<ul>
<li><p>A slow application</p>
</li>
<li><p>The browser crashing</p>
</li>
<li><p>A poor user experience</p>
</li>
</ul>
<p>For example, a component might create a “resize” event listener when it mounts, but forgets to remove it when it unmounts. This builds up memory as the user stays longer in the application and resizes the screen.</p>
<h2 id="heading-when-does-a-component-unmount">When Does A Component Unmount?</h2>
<p>A component unmounts when it no longer exists in the DOM. This can happen if:</p>
<ol>
<li><p>A user navigates away from the page.</p>
<pre><code class="lang-typescript"> &lt;Routes&gt;
   &lt;Route path=<span class="hljs-string">"/posts"</span> element={&lt;Posts /&gt;} /&gt;
   &lt;Route path=<span class="hljs-string">"/dashboard"</span> element={&lt;Dashboard /&gt;} /&gt;
 &lt;/Routes&gt;
</code></pre>
<p> The dashboard component will unmount immediately when a user navigates from <code>/dashboard</code> to any other route in the application.</p>
</li>
<li><p>A component is conditionally rendered.</p>
<pre><code class="lang-typescript"> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
   <span class="hljs-keyword">const</span> [show, setShow] = useState(<span class="hljs-literal">true</span>);

   <span class="hljs-keyword">return</span> &lt;div&gt;{show &amp;&amp; &lt;Component /&gt;}&lt;/div&gt;;
 }
</code></pre>
<p> <code>&lt;Component /&gt;</code> will unmount when <code>show</code> becomes false.</p>
</li>
<li><p>A component key changes.</p>
<pre><code class="lang-typescript"> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
   <span class="hljs-keyword">const</span> [key, setKey] = useState(<span class="hljs-built_in">Date</span>.now());

   <span class="hljs-keyword">return</span> (
     &lt;&gt;
       &lt;button onClick={<span class="hljs-function">() =&gt;</span> setKey(<span class="hljs-built_in">Date</span>.now())}&gt;Change Key&lt;/button&gt;
       &lt;Form key={key} /&gt;
     &lt;/&gt;
   );
 }
</code></pre>
<p> The <code>&lt;Form /&gt;</code> component will unmount every time the key changes. Also note that a new <code>&lt;Form /&gt;</code> component will mount each time the key changes.</p>
</li>
</ol>
<h2 id="heading-common-causes-of-memory-leaks-and-how-to-fix-them">Common Causes Of Memory Leaks And How To Fix Them</h2>
<p>As said earlier, there will be a memory leak when resources are not removed after a component unmounts. React <code>useEffect</code> allows you to return a function that will be called when a component unmounts.</p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// code to remove resources</span>
  };
}, []);
</code></pre>
<p>You can clean any created resources in this returned function. We will go through how to clean up some of these resources.</p>
<h3 id="heading-event-listeners">Event Listeners</h3>
<p>Event listeners persist if they are not removed after a component unmounts. Look at the code below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> EventListener = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [windowWidth, setWindowWidth] = useState(<span class="hljs-number">0</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleResize</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> width = <span class="hljs-built_in">window</span>.innerWidth;
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"__ Resizing Event Listerner __"</span>, width);
      setWindowWidth(width);
    }

    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"resize"</span>, handleResize);
  }, []);

  <span class="hljs-keyword">return</span> &lt;div&gt;Width is: {windowWidth}&lt;/div&gt;;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> EventListener;
</code></pre>
<p>We do not remove the resize event listener on unmount, so every mount adds a new listener. This failure to clean up leads to a memory leak.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758149182882/58ddc026-d6b8-4120-9144-53b6d87fb63e.gif" alt="GIF shows multiple 'resize' event listeners being created each time a component mounts." class="image--center mx-auto" width="800" height="495" loading="lazy"></p>
<p>As shown in the GIF above, we log the width in the console every time we resize the window’s width. We still log the same information after component unmounts. Also, when we check the “Event Listeners” tab, the number of listeners keeps increasing by 2 instead of being just 1 each time we remount the component.</p>
<p>We see two listeners when the component mounts because React uses StrictMode in development. This helps to see side effects in the development mode. The same reason the listeners increase by 2 any time we mount the component.</p>
<p>To fix this memory leak, we need to remove the event listener in our cleanup function.</p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// previous code</span>

  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">window</span>.removeEventListener(<span class="hljs-string">"resize"</span>, handleResize);
  };
}, []);
</code></pre>
<p>The cleanup function runs when the component unmounts. This, in turn, removes our event listener and prevents a memory leak.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758149497654/f95d75b4-f41f-4bb0-806b-546555be813a.gif" alt="GIF shows a 'resize' event listener that is removed when the component unmounts." class="image--center mx-auto" width="800" height="495" loading="lazy"></p>
<p>Notice this time, nothing is shown in the console when we hide the component. Also, the resize event listener was reduced to 0 when we hid (unmounted) the component, and increased to 1 when we showed (mounted) it.</p>
<h3 id="heading-timers">Timers</h3>
<p>Timers like <code>setInterval</code> and <code>setTimeout</code> can also cause memory leaks if they are not cleared after the component unmounts. Look at this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> Timers = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [countDown, setCountDown] = useState(<span class="hljs-number">0</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"__ Set Interval __"</span>);
      setCountDown(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> prev + <span class="hljs-number">1</span>);
    }, <span class="hljs-number">1000</span>);
  }, []);

  <span class="hljs-built_in">console</span>.log({ countDown });

  <span class="hljs-keyword">return</span> &lt;div&gt;Countdown: {countDown}&lt;/div&gt;;
};
</code></pre>
<p>The interval will continue to run even after React hides or unmounts the component.</p>
<p>Note that, in React 18+, React ignores a state update when a component already unmounts.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758149517577/34868e3e-e8f6-495a-a2f8-ef0e1e745051.gif" alt="GIF shows a countdown timer component that continues to run and update state after it has been unmounted from the DOM." class="image--center mx-auto" width="800" height="495" loading="lazy"></p>
<p>In the GIF, we notice that the console stops showing "__ Outside effect ” anytime we hide/unmount the component. But the string, " Interval __”, shows every time.</p>
<p>We can fix this by using the cleanup function. All timers (<code>setInterval</code>, <code>setTimeout</code>) return a unique timer ID that we can use to clear the timer after the component unmounts.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> [countDown, setCountDown] = useState(<span class="hljs-number">0</span>);
useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> timer = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.count(<span class="hljs-string">"__ Interval __"</span>);
    setCountDown(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> prev + <span class="hljs-number">1</span>);
  }, <span class="hljs-number">1000</span>);

  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">clearInterval</span>(timer);
  };
}, []);
</code></pre>
<p>We now save the ID of the timer and use this ID to clear the interval when the component unmounts. The same method applies to <code>setTimeout</code>; save the ID and clear it with <code>clearTimeout</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758149580900/7c37c824-93db-4b73-8ff4-c45c6404bb65.gif" alt="GIF shows a countdown timer component that stops running and updating state after it unmounts." class="image--center mx-auto" width="800" height="495" loading="lazy"></p>
<h3 id="heading-subscriptions">Subscriptions</h3>
<p>When a component subscribes to external data, it’s always appropriate to unsubscribe after the component unmounts. Most data source returns a callback function to unsubscribe from such data. Take Firebase for an example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { collection, onSnapshot } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/firestore"</span>;
<span class="hljs-keyword">import</span> { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> Subscriptions = <span class="hljs-function">() =&gt;</span> {
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> unsubscribe = onSnapshot(collection(db, <span class="hljs-string">"cities"</span>), <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-comment">// Respond to data</span>
        <span class="hljs-comment">// ...</span>
    });
  }, [])

    <span class="hljs-keyword">return</span> &lt;div&gt;Subscriptions&lt;/div&gt;;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Subscriptions;
</code></pre>
<p>The <code>onSnapshot</code> function from <code>firebase/firestore</code> gets real-time updates from our database. It returns a callback function that stops listening to the DB updates. If you fail to call this function, our app continues to listen to these updates even when it no longer needs them.</p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> unsubscribe = onSnapshot(collection(db, <span class="hljs-string">"cities"</span>), <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Respond to data</span>
    <span class="hljs-comment">// ...</span>
  });

  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    unsubscribe();
  };
}, []);
</code></pre>
<p>Calling <code>unsubscribe()</code> in the returned function means we are no longer interested in listening to the data updates.</p>
<h3 id="heading-async-operations">Async Operations</h3>
<p>One common mistake is not cancelling an API call when it’s no longer needed. It's a waste of resources to allow an API call to keep running when the component unmounts. This is because the browser continues to hold references in memory until the promise resolves. Look at this example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">interface</span> Post {
  id: <span class="hljs-built_in">string</span>;
  title: <span class="hljs-built_in">string</span>;
  views: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">const</span> ApiCall = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [data, setData] = useState&lt;Post[] | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> getTodos = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        setLoading(<span class="hljs-literal">true</span>);

        <span class="hljs-built_in">console</span>.time(<span class="hljs-string">"POSTS"</span>);
        <span class="hljs-keyword">const</span> req = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"&lt;http://localhost:3001/posts&gt;"</span>);
        <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> req.json();
        <span class="hljs-built_in">console</span>.timeLog(<span class="hljs-string">"POSTS"</span>);
        setData(res.posts);
      } <span class="hljs-keyword">catch</span> (error) {
        setError(<span class="hljs-string">"Try again"</span>);
      } <span class="hljs-keyword">finally</span> {
        setLoading(<span class="hljs-literal">false</span>);
      }
    };

    getTodos();
  }, []);

  <span class="hljs-keyword">return</span> (
    &lt;div style={{ marginTop: <span class="hljs-string">"2rem"</span> }}&gt;
      &lt;p&gt;ApiCall Component&lt;/p&gt;
      {loading ? (
        &lt;p&gt;Loading...&lt;/p&gt;
      ) : error ? (
        &lt;p&gt;{error}&lt;/p&gt;
      ) : data ? (
        &lt;p&gt;Views: {data[<span class="hljs-number">0</span>].views}&lt;/p&gt;
      ) : <span class="hljs-literal">null</span>}
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ApiCall;
</code></pre>
<p>This component fetches a list of posts from our server immediately it mounts. It changes the UI based on the state of the API call:</p>
<ul>
<li><p>It displays a loading text when you click the button.</p>
</li>
<li><p>It shows an error if the API fails.</p>
</li>
<li><p>It shows the data if the API succeeds.</p>
</li>
</ul>
<p>We have a simple server that returns the list of posts. The problem with the server is that it takes three seconds for it to return the list of posts.</p>
<p>What happens when a user comes to this page but decides to leave before three seconds? (We simulate leaving the page by clicking the Hide Component button.)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758149803784/bf5cf55f-75fe-472f-9a08-653c53bdafaa.gif" alt="GIF shows a component that continues with an API call after it unmounts." class="image--center mx-auto" width="800" height="495" loading="lazy"></p>
<p>As you can see, the browser still holds a reference to the request even though it’s no longer needed.</p>
<p>A proper way to fix this is to cancel the request when the component unmounts. We can do this by using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController">AbortController</a>. We can use the <code>abort</code> method to cancel the request before it gets completed, thereby releasing memory.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">interface</span> Post {
  id: <span class="hljs-built_in">string</span>;
  title: <span class="hljs-built_in">string</span>;
  views: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">const</span> ApiCall = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// previous code</span>

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> controller = <span class="hljs-keyword">new</span> AbortController();

    <span class="hljs-keyword">const</span> getTodos = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// previous code</span>

        <span class="hljs-keyword">const</span> req = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"&lt;http://localhost:3001/posts&gt;"</span>, {
          signal: controller.signal,
        });

        <span class="hljs-comment">// previous code</span>
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-keyword">if</span> (error <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Error</span> &amp;&amp; error.name === <span class="hljs-string">"AbortError"</span>) {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Request was cancelled"</span>);
          <span class="hljs-keyword">return</span>;
        }

        setError(<span class="hljs-string">"Try again"</span>);
      } <span class="hljs-keyword">finally</span> {
        setLoading(<span class="hljs-literal">false</span>);
      }
    };

    getTodos();

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      controller.abort();
    };
  }, []);

  <span class="hljs-keyword">return</span> (
    &lt;div style={{ marginTop: <span class="hljs-string">"2rem"</span> }}&gt;
      &lt;p&gt;ApiCall Component&lt;/p&gt;
      {<span class="hljs-comment">/* previous code */</span>}
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ApiCall;
</code></pre>
<p>We created a controller to track our API request when the component mounts. We then attach the controller to our API request. Our cleanup function cancels the request if the users leave the page within three seconds.</p>
<p>We can see the result of this in the GIF below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758149811531/5497edbd-050c-4059-b088-239e8c5b65ef.gif" alt="GIF shows an API call being cancelled after its component unmounts." class="image--center mx-auto" width="800" height="495" loading="lazy"></p>
<p>Most production React applications use external libraries to fetch APIs. For example, <a target="_blank" href="https://tanstack.com/query/latest/docs/framework/react/guides/query-cancellation#using-fetch">react query</a> allows us to cancel a processing promise:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> query = useQuery({
  queryKey: [<span class="hljs-string">"todos"</span>],
  queryFn: <span class="hljs-keyword">async</span> ({ signal }) =&gt; {
    <span class="hljs-keyword">const</span> todosResponse = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/todos"</span>, { signal });
    <span class="hljs-keyword">const</span> todos = <span class="hljs-keyword">await</span> todosResponse.json();

    <span class="hljs-keyword">return</span> todos;
  },
});
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Memory leaks can significantly impact your React application's performance and user experience. You can prevent these issues by properly cleaning up resources when a component unmounts. In summary, always remember to:</p>
<ul>
<li><p>Remove event listeners with <code>removeEventListener</code>.</p>
</li>
<li><p>Clear timers with <code>clearInterval</code> and <code>clearTimeout</code>.</p>
</li>
<li><p>Unsubscribe from external data sources.</p>
</li>
<li><p>Cancel API requests using <code>AbortController</code>.</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Optimize Search in JavaScript with Debouncing ]]>
                </title>
                <description>
                    <![CDATA[ A few months ago, my manager assigned me a task: implement a search functionality across an entire page. The tricky part was that the displayed text was shown in the form of prompts, and each prompt could be truncated after two lines. If the text exc... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/optimize-search-in-javascript-with-debouncing/</link>
                <guid isPermaLink="false">68d2d63ab7bb607e13c45cf4</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ajay Yadav ]]>
                </dc:creator>
                <pubDate>Tue, 23 Sep 2025 17:17:46 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758647563748/4f1c792d-5912-4bbb-9144-fcdda83d78ec.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A few months ago, my manager assigned me a task: implement a search functionality across an entire page. The tricky part was that the displayed text was shown in the form of prompts, and each prompt could be truncated after two lines.</p>
<p>If the text exceeded the limit, a split button appeared, allowing users to open the full prompt in a separate split-view section (see the illustration below for better understanding).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757771259264/66158a97-07d1-4b4f-ad24-c57c62a498ae.png" alt="A black interface with a search bar at the top left, a rating of 5/10, and navigation arrows. Below are four text boxes with dummy text snippets. On the right is a detailed explanation of &quot;Lorem Ipsum,&quot; discussing its history in the printing and typesetting industry. The page number is 1 of 7 out of 12." class="image--center mx-auto" width="1076" height="705" loading="lazy"></p>
<p>Now, if the requirement had been just plain text, I could have solved it with a simple regex-based search. In fact, inside the split view itself, I initially used a regex approach for searching along with navigation to the matches. That worked fine.</p>
<p>Since I already had a working search helper function, I thought, “Why not reuse it for the global search as well?”</p>
<p>Well, I tried that. But this time, the UI started lagging whenever I clicked the next/previous buttons in the search bar. Even the pagination controls on the top-right slowed down during navigation. To figure out a better approach, I turned to AI tools for brainstorming and came across multiple ideas and concepts.</p>
<p>As developers, we use Google daily, and naturally, I became curious about how Google’s search works under the hood. I opened up Chrome DevTools, started typing in Google’s search bar, and noticed something interesting.</p>
<p>While Google Search updates results in real-time with each keystroke, we don’t have Google’s server power. In our apps, debouncing is a practical way to avoid unnecessary API calls and improve performance. That idea matched exactly what ChatGPT had suggested to me earlier.</p>
<p>So, I applied a similar approach to my project and finally delivered the feature using debouncing, along with React hooks like <code>useTransition</code> and <code>useDeferredValue</code>. That’s how the idea for this article came out.</p>
<p>In this article, I’ll show you how to optimize your application’s performance by implementing the debounce technique.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-problem-without-debouncing">Problem Without Debouncing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-debouncing">What is Debouncing</a>?</p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-debouncing-in-javascript">How to Implement Debouncing in JavaScript</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-benefits-of-using-debouncing-in-search">Benefits of Using Debouncing in Search</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-common-mistakes-to-avoid">Common Mistakes to Avoid</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-before-we-end">Before We End</a></p>
</li>
</ul>
<p>Let’s dig in.</p>
<h2 id="heading-problem-without-debouncing">Problem Without Debouncing</h2>
<p>Imagine you’re building a search bar that fetches results from an API. Every time the user types a letter, the search bar immediately makes a new request.</p>
<p>If someone types the word “JavaScript”, that means 10 separate API calls will be fired — one for each character.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758013146153/204187ef-1757-490b-8b29-7d11951c88b3.png" alt="the Google search page with &quot;javascript&quot; in the input box" class="image--center mx-auto" width="2047" height="1506" loading="lazy"></p>
<p>Now, in Google Search, results update in real-time with every keystroke. But unlike Google, we don’t have massive infrastructure to handle that load. In most applications, firing a request for every single character quickly becomes inefficient.</p>
<p>At first, this might not seem like a big deal, but in practice it leads to serious problems. The browser has to manage a flood of unnecessary requests, the server gets overloaded with repeated calls, and the user ends up with a laggy or inconsistent experience. The whole interface feels heavy and unresponsive.</p>
<p>This is exactly the situation I ran into when I reused my simple regex-based search function for the global search. It worked fine for a small prompt inside the split view, but when applied at a larger scale with navigation buttons and pagination, the UI started freezing and slowing down.</p>
<h2 id="heading-what-is-debouncing">What is Debouncing?</h2>
<p>Debouncing is a technique, not a programming language feature. It’s simply a way to control how often a function gets called. Instead of running the function every single time an event happens, you delay its execution.</p>
<p>If the event keeps firing during that delay, the timer resets. The function only runs when the user finally pauses.</p>
<p>Think about typing into a search bar. Without debouncing, the app would make a request for every keystroke. With debouncing, the app waits until the user stops typing for a short time—say 300 milliseconds and then makes just one request with the final input.</p>
<p>Behind the scenes, this is usually implemented with <code>setTimeout</code> and <code>clearTimeout</code>. A timer starts when the event occurs, and if another event happens before the timer ends, the timer is cleared and restarted. Only when the user stops typing for the specified delay does the function execute.</p>
<h2 id="heading-how-to-implement-debouncing-in-javascript">How to Implement Debouncing in JavaScript</h2>
<p>As I mentioned earlier, debouncing is not tied to any specific programming language. It’s simply a concept that can be implemented using timers. In JavaScript, we typically use <code>setTimeout</code> and <code>clearTimeout</code> to achieve this.</p>
<p>Here’s a simple example of a debounce function in JavaScript:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">debounce</span>(<span class="hljs-params">fn, delay</span>) </span>{
  <span class="hljs-keyword">let</span> timer;
  <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">...args</span>) </span>{
    <span class="hljs-built_in">clearTimeout</span>(timer);
    timer = <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
      fn.apply(<span class="hljs-built_in">this</span>, args);
    }, delay);
  };
}
</code></pre>
<p>We start with a function <code>debounce</code> that takes two arguments:</p>
<ul>
<li><p><code>fn</code> is the function we want to control, such as the API call.</p>
</li>
<li><p><code>delay</code> is how long we want to wait before actually running <code>fn</code>.</p>
</li>
</ul>
<p>Inside, we declare a variable <code>timer</code>. This will hold the reference to the <code>setTimeout</code>.</p>
<p>The <code>debounce</code> function then returns another function. This returned function is the one that will actually run whenever an event (like typing in the input or API call) happens.</p>
<p>Every time the user types, the first thing you do is <code>clearTimeout(timer)</code>. This cancels any previously scheduled function call. Then you create a new timeout with <code>setTimeout</code>.</p>
<p>If the user keeps typing before the delay finishes, the old timer is cleared and restarted. Only when they pause long enough does the timeout finish and <code>fn</code> gets executed.</p>
<p>Did you notice how I used <code>fn.apply(this, args)</code>? That’s just a safe way of calling the original function with the correct <code>this</code> context and passing along all arguments.</p>
<p>Now here’s how you use it in practice:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchResults</span>(<span class="hljs-params">query</span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Fetching results for:"</span>, query);
  <span class="hljs-comment">// Here you could call your API</span>
}

<span class="hljs-comment">// Wrap it with debounce</span>
<span class="hljs-keyword">const</span> debouncedSearch = debounce(fetchResults, <span class="hljs-number">300</span>);

<span class="hljs-comment">// Attach to input event</span>
<span class="hljs-keyword">const</span> input = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"search"</span>);
input.addEventListener(<span class="hljs-string">"input"</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  debouncedSearch(e.target.value);
});
</code></pre>
<ol>
<li><p><code>fetchResults</code> is our actual search function. Normally, it would run for every keystroke.</p>
</li>
<li><p>We wrap it with <code>debounce</code> and set a delay of 300ms. That means it won’t run until the user stops typing for 300ms.</p>
</li>
<li><p>On every <code>input</code> event, instead of calling <code>fetchResults</code> directly, we call <code>debouncedSearch</code>. This ensures only the debounced version of the function executes.</p>
</li>
</ol>
<p>So if a user types “hello,” instead of five API calls, only one or two will fire once they pause.</p>
<h2 id="heading-benefits-of-using-debouncing-in-search">Benefits of Using Debouncing in Search</h2>
<p>Using debouncing in a search feature may feel like a small optimization, but it has a big impact. The most obvious benefit is performance.</p>
<p>Instead of making a request for every single keystroke, your app waits until the user pauses, which saves both browser and server resources. The UI feels much smoother because it isn’t constantly being interrupted by unnecessary calls.</p>
<p>Debouncing also improves scalability. If hundreds or thousands of users are typing at once, you’re cutting down a huge number of wasted API calls. That means your backend can handle more users without getting overloaded.</p>
<p>There’s also an indirect benefit for SEO and analytics. When your app performs efficiently and feels snappy, users stay longer, interact more, and bounce less. This kind of responsiveness can make a big difference in how people perceive the quality of your product.</p>
<h2 id="heading-common-mistakes-to-avoid">Common Mistakes to Avoid</h2>
<p>While debouncing is powerful, there are a few mistakes developers often make. One common issue is setting the delay too high. If you make users wait one or two seconds before seeing results, the search will feel unresponsive.</p>
<p>On the other hand, a delay that’s too short may not reduce calls enough to be useful. A sweet spot is usually between 300–500 milliseconds, but it depends on your use case.</p>
<p>Another mistake is forgetting to clear old timers. Without clearing, your app may still execute older, outdated calls, which can lead to glitches or memory leaks. That’s why <code>clearTimeout</code> is just as important as <code>setTimeout</code> in any debounce function.</p>
<p>It’s also important to think about edge cases. What happens if the input is cleared quickly? Or if someone pastes a long string instead of typing? Testing these cases ensures your debounce function works smoothly in real-world scenarios.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>When I first encountered the challenge of building a global search, I thought I could simply reuse my basic regex-based solution. However, the UI soon began to lag, and the user experience declined. It's surprising how such a small concept can significantly impact performance.</p>
<p>Debouncing ensures that your functions run at the right time, not every time. Whether you’re building a simple JavaScript app or working with React and Next.js, this technique helps reduce unnecessary calls, improves performance, and keeps your app scalable.</p>
<p>So the next time you build a search bar, remember: don’t just make it work, make it efficient.</p>
<h2 id="heading-before-we-end">Before We End</h2>
<p>I hope you found this article insightful. I’m Ajay Yadav, a software developer and content creator.</p>
<p>You can connect with me on:</p>
<ul>
<li><p><a target="_blank" href="https://x.com/atechajay">Twitter/X</a> and <a target="_blank" href="https://www.linkedin.com/in/atechajay/">LinkedIn</a>, where I share insights to help you improve 0.01% each day.</p>
</li>
<li><p>Check out my <a target="_blank" href="https://github.com/ATechAjay">GitHub</a> for more projects.</p>
</li>
<li><p>I also run a Hindi <a target="_blank" href="http://youtube.com/@atechajay">YouTube Channel</a> where I share content about careers, software engineering, and technical writing.</p>
</li>
</ul>
<p>See you in the next article — until then, keep learning!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Optimize Next.js Web Apps for Better Performance ]]>
                </title>
                <description>
                    <![CDATA[ As engineers, we often get so carried away with other aspects of development that we overlook how users perceive and interact with our applications. This oversight can result in users leaving the app almost as soon as they arrive, leading to higher b... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/optimize-nextjs-web-apps-for-better-performance/</link>
                <guid isPermaLink="false">6776a323218b455d646035b2</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ web performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ web ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Performance Optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayantunji Timilehin ]]>
                </dc:creator>
                <pubDate>Thu, 02 Jan 2025 14:30:59 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1735828217839/b65374be-d891-4f19-a359-f84f2ac8f3b9.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As engineers, we often get so carried away with other aspects of development that we overlook how users perceive and interact with our applications. This oversight can result in users leaving the app almost as soon as they arrive, leading to higher bounce rates and minimal engagement.</p>
<p>At its core, every business thrives on delivering value to its users. When users are unable to access this value due to poor performance, it ultimately impacts the business's success. Slow load times, among other factors, frustrate users and drive them away before they even get a chance to engage.</p>
<p>Optimizing performance is more than just a technical detail – it’s also a critical part of creating a successful application. Without it, even the best features can go unnoticed if users don’t stick around long enough to see them.</p>
<p>In this article, we’ll explore key approaches to optimize your Next.js application, making it faster and more efficient.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-building-a-performant-app">Building a Performant Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-optimize-your-applications">How to Optimize Your Applications</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-key-techniques-to-optimize-performance">Key Techniques To Optimize Performance</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-using-the-nextjs-image-component">Using The Next.js Image Component</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-optimizing-third-party-scripts-with-the-nextjs-script-component">Optimizing Third-Party Scripts with the Next.js Script Component</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-remove-unused-packagesdependencies">Remove Unused Packages/Dependencies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-caching-and-incremental-static-regeneration-isr">Caching and Incremental Static Regeneration (ISR)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-caching-frequently-used-content">Caching Frequently Used Content</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-font-optimization-with-nextfont">Font Optimization With next/font</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-6-lazy-loading-and-code-splitting">Lazy Loading And Code Splitting</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lazy-loading-in-nextjs">Lazy Loading in Next.js</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-code-splitting">Code Splitting</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-building-a-performant-application">Building a Performant Application</h2>
<p>Making your apps more performant means striking the right balance between speed, responsiveness, and efficient use of resources. You should strive to create an application that delivers value and keeps users satisfied.</p>
<p>Building a performant app is about making sure the app feels smooth and intuitive so that there are no frustrating lags when a user clicks buttons, scrolls, or navigates around. You’ll also want to make sure that data loads or updates without unnecessary delays.</p>
<h2 id="heading-how-to-optimize-your-applications">How to Optimize Your Applications</h2>
<p>The first step in optimizing your application is identifying problem areas. A number of tools and packages can help you analyze your application's performance effectively. Here's how you can use them:</p>
<h3 id="heading-using-npm-run-build">Using <code>npm run build</code></h3>
<p>When you run <code>npm run build</code>, Next.js creates a production-ready version of your application and gives a detailed breakdown of your pages. This includes:</p>
<ul>
<li><p><strong>Size</strong>: The size of the JavaScript files for each route. Highlighting any routes that are too large and could slow things down. Smaller page sizes generally result in faster load times while large pages might take longer to download, especially for users with slower network connections.</p>
</li>
<li><p><strong>First Load Js</strong>: This column provides information about the total amount of JavaScript the browser needs to download and execute to fully render the page for the first time. Large <strong>First Load JS</strong> values</p>
<p>  cause Slower Time-to-Interactive (TTI).</p>
</li>
</ul>
<p>Running this command produces an analysis like below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734639730677/cfd1f858-a9df-4e6c-af28-454857309156.png" alt="Example result of running npm run build" class="image--center mx-auto" width="984" height="300" loading="lazy"></p>
<h3 id="heading-using-nextbundle-analyzer">Using <code>@next/bundle-analyzer</code></h3>
<p>The <a target="_blank" href="https://www.npmjs.com/package/@next/bundle-analyzer">bundle analyzer</a> is a package provided by Next.js to analyze the size of JavaScript bundles by providing a visual representation of the application’s module and dependencies. Here’s how to use the package:</p>
<p>First, install the package by running this command:</p>
<pre><code class="lang-bash">npm install @next/bundle-analyzer
</code></pre>
<p>Or you can use yarn:</p>
<pre><code class="lang-bash">yarn add @next/bundle-analyzer
</code></pre>
<p>Then add the <code>@next/bundle-analyzer</code> configuration to your <code>next.config.js</code> file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> withBundleAnalyzer = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@next/bundle-analyzer'</span>)({
  <span class="hljs-attr">enabled</span>: process.env.ANALYZE === <span class="hljs-string">'true'</span>,
});

<span class="hljs-built_in">module</span>.exports = withBundleAnalyzer({
  <span class="hljs-comment">// other Next.js config options here</span>
});
</code></pre>
<p>To analyze your application bundles while generating a production build, run the following command:</p>
<pre><code class="lang-bash">ANALYZE=<span class="hljs-literal">true</span> npm run build
</code></pre>
<p>For a step-by-step guide on how to use the bundle analyzer effectively, check out this detailed <a target="_blank" href="https://www.youtube.com/watch?v=EIGmcxwbbZw">video tutorial</a></p>
<h3 id="heading-browser-tools">Browser tools</h3>
<p>Finally, modern browsers, including Google Chrome, Firefox, and Edge, offer powerful tools to analyze and improve your application's performance. Features like the Performance Tab help you record and visualize how your application runs, pinpointing issues like slow rendering or long tasks.</p>
<p>You can also use tools like Lighthouse (available in Chrome and Edge) to generate automated audits, highlighting problems such as large assets and unoptimized resources.</p>
<p>To access the <strong>Lighthouse</strong> and <strong>Performance</strong> tabs:</p>
<ol>
<li><p>Open your browser's developer tools by right-clicking anywhere on the browser and selecting the <strong>Inspect</strong> option or pressing <strong>Command + Option + I</strong> (on Mac) or <strong>Ctrl + Shift + I</strong> (on Windows).</p>
</li>
<li><p>Look at the top menu in the developer tools.</p>
</li>
<li><p>If you don’t see the <strong>Lighthouse</strong> or <strong>Performance</strong> tabs right away, click the <strong>double right arrow (&gt;&gt;)</strong> to reveal hidden tabs.</p>
</li>
<li><p>Select the desired tab to start analyzing performance or generating a Lighthouse report.</p>
</li>
</ol>
<p>Here is an example of a generated audit in the Performance tab on Chrome</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735616911745/e5f09934-df99-40fc-b194-a292a21a4517.png" alt="image of the performance tab on chrome browser" class="image--center mx-auto" width="1130" height="966" loading="lazy"></p>
<p>Here’s another image showing the generated audit by lighthouse</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735617075187/dfde608b-eeb7-443d-81c6-56ff2a6dd92b.png" alt="dfde608b-eeb7-443d-81c6-56ff2a6dd92b" class="image--center mx-auto" width="1124" height="1522" loading="lazy"></p>
<h2 id="heading-key-techniques-to-optimize-performance">Key Techniques to Optimize Performance</h2>
<h3 id="heading-1-using-the-nextjs-image-component">1.) Using The Next.js <code>Image</code> Component</h3>
<p>Images often account for the largest portion of page weight, directly affecting load times and user experience. Large images slow down rendering and ultimately, increase bandwidth usage.</p>
<p>Next.js has a built-in <code>Image</code> component that automatically optimizes images, making it very useful for web performance. It takes care of resizing, lazy loading, and format optimization, so images are served in the most performant format (like .WebP) when the browser supports it.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Image <span class="hljs-keyword">from</span> <span class="hljs-string">'next/image'</span>;

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Image</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">"/house.jpg"</span>
      <span class="hljs-attr">alt</span>=<span class="hljs-string">"House Image"</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">{700}</span>
      <span class="hljs-attr">height</span>=<span class="hljs-string">{500}</span>
      <span class="hljs-attr">priority</span>=<span class="hljs-string">{false}</span> // <span class="hljs-attr">Lazy</span> <span class="hljs-attr">loads</span> <span class="hljs-attr">the</span> <span class="hljs-attr">image</span> <span class="hljs-attr">by</span> <span class="hljs-attr">default</span>
    /&gt;</span></span>
</code></pre>
<p>In the snippet above,</p>
<ul>
<li><p><code>src="/house.jpg"</code>: This points to the image file's location, which is in the <code>public</code> folder. Images in the <code>/public</code> directory are served statically, so you don’t need extra configuration.</p>
</li>
<li><p><code>alt="House Image"</code>: The <code>alt</code> text (just like in the native HTML <code>image</code> element) provides a description of the image, which is great for accessibility (like screen readers) and also helps with SEO.</p>
</li>
<li><p><code>width &amp; heigh</code>t: By explicitly setting the width and height, Next.js can calculate the space the image will occupy on the page before it loads. This prevents the page layout from shifting as the image loads, which improves user experience and boosts performance metrics like <a target="_blank" href="https://blog.hubspot.com/marketing/cumulative-layout-shift">Cumulative Layout Shift</a> (as shown in the image above).</p>
</li>
<li><p><code>priority={false}</code>: This ensures the image will only load when it's near the user's viewport conserving the bandwidth and improving page load times for non-critical images. However, for important images that should load immediately (like those visible as soon as the page opens), you can set <code>priority={true}</code> to bypass lazy loading and ensure the image loads as quickly as possible.</p>
</li>
</ul>
<p>One of the key advantages of the Next.js <code>Image</code> component is its built-in <strong>lazy loading</strong> feature. This means that images won’t be loaded until they are actually needed (when they enter the viewport). By only loading images that are about to be viewed, performance is improved and pages can load faster, even with many high-quality images.</p>
<h3 id="heading-2-optimizing-third-party-scripts-with-the-nextjs-script-component">2.) Optimizing Third-Party Scripts with the Next.js Script Component</h3>
<p>Third-party scripts, such as analytics tools or advertising networks, can heavily affect your application's performance if not properly managed. Next.js has a <strong>Script</strong> component that makes it easy to load scripts efficiently, giving you control over how and when they load.</p>
<p>The <code>Script</code> component allows you to define a <strong>loading strategy</strong> for scripts, determining when and how they are fetched and executed. By prioritizing or deferring scripts based on their importance, you can improve the overall performance and user experience of your application.</p>
<ul>
<li><p><code>beforeInteractive</code><strong>:</strong> Use this strategy for scripts that must load before the page becomes interactive, like essential analytics or monitoring tools.</p>
</li>
<li><p><code>afterInteractive</code>: When you use this strategy, the script loads after the page becomes interactive, which is the default behavior. This is ideal for scripts that add functionality but aren’t essential for initial rendering.</p>
</li>
<li><p><code>lazyOnload</code>: Defers loading the script until all other page resources have finished loading. This is perfect for non-essential scripts like ads or social media widgets.</p>
</li>
</ul>
<pre><code class="lang-javascript">&lt;Script src=<span class="hljs-string">"https://example.com/non-essential.js"</span> strategy=<span class="hljs-string">"lazyOnload"</span> /&gt; <span class="hljs-comment">//Pass the strategy as a prop to the component</span>
</code></pre>
<p>By leveraging the Next.js <code>Script</code> component, you can prevent scripts from blocking critical rendering, reducing load times and improve <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Glossary/Time_to_interactive">Time to Interactive</a> (TTI).</p>
<h3 id="heading-3-remove-unused-packagesdependencies">3.) Remove Unused Packages/Dependencies</h3>
<p>Over time, as you build and maintain your project, unused dependencies can pile up in your codebase. These unnecessary packages increase the size of your project, slow down installation times, and make the code harder to maintain. Cleaning up these unused dependencies is essential for optimizing your application's performance and keeping your codebase clean.</p>
<p>The <a target="_blank" href="https://www.npmjs.com/package/depcheck">depcheck</a> tool is a great way to identify and remove unused dependencies from your project. It analyzes your <code>package.json</code> and the project files to find unused dependencies, unused devDependencies, and missing dependencies.</p>
<p>You can run a <code>depcheck</code> like this:</p>
<pre><code class="lang-bash">npx depcheck
</code></pre>
<p>After identifying the unused dependencies, you can remove them by running:</p>
<pre><code class="lang-bash">npm uninstall &lt;package-name&gt;
</code></pre>
<p>or with yarn:</p>
<pre><code class="lang-bash">yarn remove &lt;package-name&gt;
</code></pre>
<p>Regularly running <code>depcheck</code> is a simple yet effective way to keep your project clean and efficient.</p>
<h3 id="heading-4-caching-and-incremental-static-regeneration-isr">4.) Caching and Incremental Static Regeneration (ISR)</h3>
<p>When you find yourself running the same calculations or database queries repeatedly, you should consider caching. It’s a simple yet powerful way to boost your web application's performance, especially for content that doesn’t change often. By storing frequently accessed data in a cache, you can avoid unnecessary processing and speed up load times.</p>
<p>In Next.js, you can take this a step further with Incremental Static Regeneration (ISR), which lets you serve static content instantly while keeping it fresh behind the scenes.</p>
<p><strong>Incremental Static Regeneration (ISR)</strong> in Next.js lets you update static pages without rebuilding the whole site. Here's how it works:</p>
<ol>
<li><p><strong>Build time generation</strong>: ISR generates pages when the site is built.</p>
</li>
<li><p><strong>Caching</strong>: It stores the pages so they load quickly when users visit.</p>
</li>
<li><p><strong>Background updates</strong>: When content changes, ISR updates the pages behind the scenes without affecting users.</p>
</li>
<li><p><strong>Dynamic updates</strong>: It combines the fast loading of static pages with the ability to update content regularly.</p>
</li>
</ol>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> fetchData();

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: { data },
    <span class="hljs-comment">//regenerate the page every 20 seconds.</span>
    <span class="hljs-attr">revalidate</span>: <span class="hljs-number">20</span>,
  };
}

<span class="hljs-comment">//pre-render the page as static content</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyPage</span>(<span class="hljs-params">{ data }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>My Page<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{data}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyPage;
</code></pre>
<h3 id="heading-caching-frequently-used-content">Caching Frequently Used Content</h3>
<p>For websites with pages that get a lot of visitors, like product listings or blog posts, it's important to keep the content fast and up-to-date.</p>
<p>Caching helps achieve this by saving a copy of the page so it doesn't need to be created from scratch each time someone visits. The browser or server will store this cached page for a set amount of time, which is controlled by caching headers. Meanwhile, ISR (Incremental Static Regeneration) ensures that the page can be updated in the background when necessary, without needing to rebuild the entire site.</p>
<p>In applications with lots of data, caching can also speed up the process by storing API responses. This way, when users request the same data again, they can get it quickly from the cache instead of waiting for it to be fetched anew. Tools like Vercel and Content Delivery Networks (CDNs) help by storing these cached pages in multiple locations around the world, so visitors can access them faster.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> fetchData();

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: { data },
    <span class="hljs-comment">// Regenerate page at most once every 30 seconds</span>
    <span class="hljs-attr">revalidate</span>: <span class="hljs-number">30</span>,
    <span class="hljs-comment">// Cache for 1 hour at the CDN level</span>
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">'Cache-Control'</span>: <span class="hljs-string">'public, max-age=3600, must-revalidate'</span>,
    },
  };
}
</code></pre>
<p>Here, the page regenerates every 30 seconds and is cached at the CDN level for one hour. The <code>Cache-Control</code> header tells the CDN and browser to cache the page for 1 hour and revalidate it afterward.</p>
<p>For a deeper dive into caching and its role in web performance, check out this insightful <a target="_blank" href="https://www.freecodecamp.org/news/caching-vs-content-delivery-network/">freeCodeCamp article on Caching vs. Content Delivery Networks</a>.</p>
<h3 id="heading-5-font-optimization-with-nextfont">5.) Font Optimization With <code>next/font</code></h3>
<p>The <code>next/font</code> module in Next.js automatically handles font loading for improved performance, so you don’t need to manually configure or use extra libraries. It loads only the essential parts of the font, which results in faster page load times.</p>
<p>To further reduce the font file size, you can provide the <code>subsets</code> array which ensures fewer bytes are transferred and pages load quickly.</p>
<p>Here’s how it works:</p>
<ul>
<li><p><strong>Automatic font loading</strong>: The module optimizes font loading automatically, making sure fonts are served in the most efficient way, improving performance without extra effort.</p>
</li>
<li><p><strong>Subsetting fonts</strong>: You can specify the exact font characters needed for your app.</p>
</li>
<li><p><strong>Font display strategy</strong>: The font-display strategy determines how text is shown to the user while fonts are loading. Next.js typically uses the <code>swap</code> strategy by default, but you can manually configure it if necessary. The most common strategies are <code>swap</code> <code>fallback</code> <code>optional</code> and <code>block</code>.</p>
</li>
<li><pre><code class="lang-javascript">  <span class="hljs-keyword">import</span> { Inter } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/font/google'</span>

  <span class="hljs-keyword">const</span> inter = Inter({
    <span class="hljs-attr">subsets</span>: [<span class="hljs-string">'latin'</span>, <span class="hljs-string">'latin-ext'</span>], <span class="hljs-comment">// Load only the Latin and extended Latin subsets</span>
    <span class="hljs-attr">weight</span>: <span class="hljs-string">'400'</span>, <span class="hljs-comment">// Choose the specific weight you need</span>
    <span class="hljs-attr">style</span>: <span class="hljs-string">'normal'</span>, <span class="hljs-comment">// Specify the style if needed</span>
  })

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Page</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{inter.className}</span>&gt;</span>Hello World<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  }
</code></pre>
</li>
</ul>
<p>The snippet above uses the Next.js built-in tool for Google Fonts. Instead of adding the font link in your HTML or using a third-party library, you can import it directly like this for ease and efficiency.s</p>
<ul>
<li><p><strong>subsets:</strong> Tells the app to load only the characters needed. Skipping other character sets like Cyrillic (used in Russian) or Greek, avoids downloading extra, unnecessary data, which keeps your app lightweight and faster to load.</p>
</li>
<li><p><strong>weight:</strong> Instead of loading all font weights (e.g., Bold, Light), you only bring in Regular (400). This reduces the overall size.</p>
</li>
<li><p><strong>style:</strong> Stick with the standard style (no fancy italics). This also trims down what’s downloaded.</p>
</li>
</ul>
<h3 id="heading-6-lazy-loading-and-code-splitting">6.) Lazy Loading and Code Splitting</h3>
<p>When building web apps, you want to make sure your users don’t wait too long for your pages to load. A big part of this involves reducing how much JavaScript is loaded when the page first opens. Two techniques that help with this are <strong>lazy loading</strong> and <strong>code splitting</strong>, both of which Next.js makes easy to use.</p>
<h4 id="heading-lazy-loading-in-nextjs">Lazy Loading in Next.js</h4>
<p>Think of lazy loading like waiting to download a movie only when you decide to watch it. Imagine you have a large component like a chart or a map that users only see after interacting with a page. Instead of loading it upfront, you can tell Next.js to load it only when it’s needed using <code>next/dynamic</code>.</p>
<h4 id="heading-code-splitting-in-nextjs">Code Splitting in Next.js</h4>
<p>Code splitting breaks your JavaScript into smaller pieces (called bundles), so users only load what’s necessary. For example, if a user visits your homepage, there’s no need to load JavaScript for other pages like "About Us" or "Dashboard". It typically happens during the build process or dynamically at runtime.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> dynamic <span class="hljs-keyword">from</span> <span class="hljs-string">'next/dynamic'</span>

<span class="hljs-comment">// Load HeavyComponent only when it’s rendered</span>
<span class="hljs-keyword">const</span> HeavyComponent = dynamic(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./HeavyComponent'</span>), { <span class="hljs-attr">ssr</span>: <span class="hljs-literal">false</span> })

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Welcome Home!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">HeavyComponent</span> /&gt;</span> {/* This loads only when rendered */}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}
</code></pre>
<p>In the above code, <code>dynamic</code> dynamically imports the component only when needed. <code>ssr: false</code> disables server-side rendering for the component, which can save resources if the component doesn’t need to be pre-rendered.</p>
<p>Next.js automatically splits code by page, meaning each page only loads the necessary JavaScript when accessed, improving load times. For more granular control, <code>next/dynamic</code> allows you to dynamically import specific components, ensuring they are loaded lazily only when needed. While Next.js handles page-level code splitting by default, using <code>next/dynamic</code> gives you the flexibility to apply component-level splitting, optimizing resource loading and enhancing performance.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Creating a high-performance application is a very important aspect of any business. A faster and more efficient application enhances user engagement, lowers bounce rates, and boosts SEO rankings, which all contribute to business growth and customer satisfaction.</p>
<p>By utilizing these techniques we discussed in this guide, you can provide a smooth user experience while maintaining optimal efficiency behind the scenes.</p>
<p>Remember, every second saved in load time translates to happier users and, ultimately, better business outcomes.</p>
<p>Thank you for reading!</p>
<p>Want to connect with me?</p>
<ul>
<li><p>Twitter / X: <a target="_blank" href="https://x.com/Timi471">@timi471</a></p>
</li>
<li><p>Linkedin: <a target="_blank" href="https://www.linkedin.com/in/timilehin-micheal/">Ayantunji Timilehin</a></p>
</li>
<li><p>Email: ayantunjitimilehin@gmail.com</p>
</li>
</ul>
<h3 id="heading-references">References</h3>
<ul>
<li><p><a target="_blank" href="https://nextjs.org/docs/pages/building-your-application/optimizing">Next.Js Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/caching-vs-content-delivery-network/">Caching-vs-content-delivery-network</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=EIGmcxwbbZw">Using next/bundle-analyzer</a></p>
</li>
<li><p><a target="_blank" href="https://blog.hubspot.com/marketing/cumulative-layout-shift">Cumulative layout shift</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Improve and Restructure Your Codebase with AI Tools & Version Control ]]>
                </title>
                <description>
                    <![CDATA[ A codebase can become messy and hard to manage over time. This happens because of quick fixes, outdated features, or just not enough time to clean things up. When code becomes difficult to read or change, it slows down progress and can even cause bug... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/improve-and-restructure-codebase-with-ai-tools/</link>
                <guid isPermaLink="false">6720f7fb2e452955f9433899</guid>
                
                    <category>
                        <![CDATA[ best practices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Programming Tips ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ improve performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ version control ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Pull Requests ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwadamisi Samuel ]]>
                </dc:creator>
                <pubDate>Tue, 29 Oct 2024 14:58:03 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1730194934749/feb606d0-bbbd-43ae-a58c-5932d8c2d76c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A codebase can become messy and hard to manage over time. This happens because of quick fixes, outdated features, or just not enough time to clean things up.</p>
<p>When code becomes difficult to read or change, it slows down progress and can even cause bugs. To keep a codebase healthy and easy to work with, you’ll need to take care of it.</p>
<p>Improving and organizing old code can feel like a big task, but there are tools and methods that can make it easier. This guide will show how to refresh your codebase step by step which will make it simpler to work with and less likely to cause issues.</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-how-to-review-your-code-effectively">How to Review Your Code Effectively</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-identify-technical-debt-and-problem-areas-in-code">How to Identify Technical Debt and Problem Areas in Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-measure-code-quality-with-code-analysis-tools">How to Measure Code Quality with Code Analysis Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-ai-tools-to-help-you-improve-your-code">AI Tools to Help You Improve Your Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-version-control-best-practices-for-code-changes">Version Control Best Practices for Code Changes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-how-to-review-your-code-effectively">How to Review Your Code Effectively</h2>
<p>Code reviews are essential for catching issues early, improving readability, and ensuring long-term maintainability. Reviewing your own code or someone else’s involves more than just scanning for errors – you’ll also want to make sure each part is clear, efficient, and follows good practices.</p>
<p>Here’s a step-by-step approach to help you review code effectively, with practical strategies, tools, and what to look for during the process.</p>
<h3 id="heading-strategies-for-effective-code-review">Strategies for Effective Code Review</h3>
<ol>
<li><p><strong>Break Down the Review Process:</strong> Reviewing code all at once can be overwhelming, especially in large projects. Focus on small sections of the codebase at a time, such as individual functions or modules. This approach helps you examine each part closely and avoids missing issues that could be overlooked in a quick scan.</p>
</li>
<li><p><strong>Review for Clarity and Simplicity:</strong> Good code should be easy to read and understand. When reading through the code:</p>
<ul>
<li><p><strong>Variable and Function Names:</strong> Are variable names descriptive enough to convey their purpose? Long, unclear names make code harder to follow.</p>
</li>
<li><p><strong>Function Length:</strong> Keep functions short and focused on one task. Long functions are harder to debug and maintain.</p>
</li>
<li><p><strong>Comments and Documentation:</strong> Comments should explain <em>why</em> something is done rather than <em>what</em> is happening, which should be clear from the code itself. For instance, avoid excessive commenting on trivial lines and focus on complex logic or business rules.</p>
</li>
</ul>
</li>
<li><p><strong>Check for Code Reusability and Modularity:</strong> Look for repeated code or functions performing multiple tasks. By modularizing code, you make it easier to test, update, and reuse. In a review, look for:</p>
<ul>
<li><p><strong>Duplicate Code:</strong> Repeated code can often be refactored into a function.</p>
</li>
<li><p><strong>Single Responsibility:</strong> Each function should handle one task, making it easier to maintain and update.</p>
</li>
</ul>
</li>
<li><p><strong>Examine Error Handling and Edge Cases:</strong> Robust code should handle unexpected inputs or errors gracefully. During a review, think about potential edge cases that could break the code:</p>
<ul>
<li><p><strong>Null or Undefined Values:</strong> Does the code check for undefined values where needed?</p>
</li>
<li><p><strong>Out-of-Range Errors:</strong> Ensure array indexes and calculations won’t produce errors with edge cases.</p>
</li>
<li><p><strong>Error Messages:</strong> Make sure error handling is meaningful, with clear error messages where applicable.</p>
</li>
</ul>
</li>
<li><p><strong>Look for Performance Issues:</strong> Performance may not always be critical, but it’s good to check for potential bottlenecks. Look for:</p>
<ul>
<li><p><strong>Loop Optimization:</strong> Avoid deeply nested loops or repeated work inside loops.</p>
</li>
<li><p><strong>Database Queries:</strong> Minimize unnecessary database calls.</p>
</li>
<li><p><strong>Heavy Computation in the Main Thread:</strong> Move any heavy processing outside the main application thread if possible.</p>
</li>
</ul>
</li>
<li><p><strong>Ensure Consistency with Coding Standards:</strong> Following a consistent coding style improves readability across the team. Many teams use linters or style guides to enforce these standards. Look for:</p>
<ul>
<li><p><strong>Code Format:</strong> Consistent indentation, spacing, and use of braces.</p>
</li>
<li><p><strong>Naming Conventions:</strong> Follow agreed naming conventions (camelCase, snake_case, and so on) consistently.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-tools-to-assist-with-code-reviews">Tools to Assist with Code Reviews</h3>
<p>There are a number of tools out there that can help streamline your code reviews, whether you’re checking your own code or collaborating with others:</p>
<h4 id="heading-1-linters-like-eslint-and-pylint"><strong>1. Linters (like ESLint and Pylint)</strong></h4>
<p>Linters check for syntax errors, code smells, and style guide violations. They are especially useful for catching minor issues, like inconsistent formatting or unused variables. We will discuss ESLint more in an upcoming section.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Example: Run ESLint on a JavaScript project</span>
npx eslint src/
</code></pre>
<h4 id="heading-2-static-analysis-tools-like-sonarqube"><strong>2. Static Analysis Tools (like SonarQube)</strong></h4>
<p>These tools analyze code for deeper issues like security vulnerabilities, code duplication, and complex functions that might need refactoring.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Configuring SonarQube to scan a project</span>
sonar.projectKey=my_project
sonar.sources=src
sonar.host.url=http://localhost:9000
sonar.login=my_token
</code></pre>
<h4 id="heading-3-automated-testing-tools"><strong>3. Automated Testing Tools</strong></h4>
<p>Running tests can verify that code changes don’t introduce new bugs. Use testing frameworks like Jest for JavaScript, PyTest for Python, or JUnit for Java to confirm your code behaves as expected.</p>
<h3 id="heading-example-of-refactoring-during-code-review">Example of Refactoring During Code Review</h3>
<p>Let’s say you encounter a long function with multiple responsibilities. The goal is to split it into smaller, focused functions. Here’s how you can do that:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Original: A single function that handles everything</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processOrder</span>(<span class="hljs-params">order</span>) </span>{
    <span class="hljs-comment">// Calculate total price</span>
    <span class="hljs-keyword">let</span> total = <span class="hljs-number">0</span>;
    order.items.forEach(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {
        total += item.price * item.quantity;
    });

    <span class="hljs-comment">// Apply discount</span>
    <span class="hljs-keyword">if</span> (order.discountCode) {
        total = total * <span class="hljs-number">0.9</span>; <span class="hljs-comment">// 10% discount</span>
    }

    <span class="hljs-comment">// Send order confirmation email</span>
    sendEmail(order.customerEmail, <span class="hljs-string">'Order Confirmation'</span>, <span class="hljs-string">'Your order total is '</span> + total);
}

<span class="hljs-comment">// Improved: Break into smaller functions for readability and reusability</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">calculateTotal</span>(<span class="hljs-params">order</span>) </span>{
    <span class="hljs-keyword">return</span> order.items.reduce(<span class="hljs-function">(<span class="hljs-params">sum, item</span>) =&gt;</span> sum + item.price * item.quantity, <span class="hljs-number">0</span>);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">applyDiscount</span>(<span class="hljs-params">total, discountCode</span>) </span>{
    <span class="hljs-keyword">return</span> discountCode ? total * <span class="hljs-number">0.9</span> : total;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendConfirmationEmail</span>(<span class="hljs-params">email, total</span>) </span>{
    sendEmail(email, <span class="hljs-string">'Order Confirmation'</span>, <span class="hljs-string">'Your order total is '</span> + total);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processOrder</span>(<span class="hljs-params">order</span>) </span>{
    <span class="hljs-keyword">let</span> total = calculateTotal(order);
    total = applyDiscount(total, order.discountCode);
    sendConfirmationEmail(order.customerEmail, total);
}
</code></pre>
<p>Breaking down the process into smaller functions makes the code cleaner, more readable, and easier to test. Each function now has a single responsibility, which helps reduce bugs and makes future updates simpler.</p>
<h2 id="heading-how-to-identify-technical-debt-and-problem-areas-in-code">How to Identify Technical Debt and Problem Areas in Code</h2>
<p>Technical debt refers to the accumulation of issues within a codebase that arise when development shortcuts are taken, often to meet tight deadlines or speed up releases. While these shortcuts may enable quicker progress initially, they lead to complications down the line.</p>
<p>Technical debt requires proactive management. If you leave it unchecked, it can reduce productivity, create bugs, and slow down development.</p>
<p>Think of technical debt like financial debt: taking on debt can be helpful in the short term, but failing to address it or pay it down will lead to greater challenges.</p>
<p>Common causes of technical debt include:</p>
<ul>
<li><p><strong>Rushed development cycles:</strong> When teams prioritize quick delivery over thorough design and testing, they may produce incomplete or hastily written code.</p>
</li>
<li><p><strong>Lack of planning for future changes:</strong> Sometimes, code is written without accounting for scalability, leading to issues as the project grows.</p>
</li>
<li><p><strong>Insufficient documentation or testing:</strong> Without proper documentation and test coverage, codebases become difficult to understand and validate over time.</p>
</li>
<li><p><strong>Outdated frameworks and dependencies:</strong> When frameworks or libraries aren’t updated, they can become incompatible with newer components or security standards, introducing risk and hindering future updates.</p>
</li>
</ul>
<h3 id="heading-types-of-technical-debt">Types of Technical Debt</h3>
<p>Technical debt manifests in different ways. Here are some common examples:</p>
<p><strong>1. Code Duplication:</strong></p>
<p>Repeated code across multiple places within a project can lead to inconsistencies, as fixing an issue or updating a feature in one area may not carry over to others. Refactoring duplicate code into reusable functions or components is an effective way to reduce this debt.</p>
<p><strong>Example:</strong> In a web application, you might find similar code for user authentication scattered across different modules. Instead, centralizing this logic into a single authentication module ensures consistent updates.</p>
<p><strong>2. Outdated Dependencies and Frameworks:</strong></p>
<p>Using old libraries or frameworks can slow down development and introduce security vulnerabilities. Over time, dependencies may lose support or become incompatible with new features, making them costly to maintain.</p>
<p><strong>Solution:</strong> Regularly update libraries and frameworks, and monitor for deprecations or vulnerabilities. This can be streamlined by using dependency managers, which help check for updates and security patches.</p>
<p><strong>3. Complex, Long Functions with Multiple Responsibilities:</strong></p>
<p>Large, complex functions that handle multiple tasks are difficult to understand, test, and modify. Known as “God functions,” these make debugging cumbersome and increase the risk of introducing new bugs.</p>
<p><strong>Solution:</strong> Follow the <strong>Single Responsibility Principle (SRP)</strong>. This means that each function or method should accomplish one task. Breaking down large functions into smaller, focused units makes the code easier to read and test.</p>
<p><strong>Example:</strong> Instead of having a single <code>processUserRequest</code> function that handles authentication, logging, and database queries, split it into three functions: <code>authenticateUser</code>, <code>logRequest</code>, and <code>queryDatabase</code>.</p>
<p><strong>4. Insufficient Error Handling:</strong></p>
<p>Code that lacks proper error handling can lead to bugs and unexpected behavior, especially in larger systems. Without clear error messages, diagnosing and fixing issues can be challenging.</p>
<p><strong>Solution:</strong> Include comprehensive error handling and ensure that meaningful error messages are displayed. Log errors in a way that helps developers track and diagnose issues.</p>
<p><strong>5. Hardcoded Values:</strong></p>
<p>Hardcoding values directly into code makes it difficult to adjust settings without modifying the source code. For example, using fixed URLs or credentials directly in the codebase can create security risks and maintenance headaches.</p>
<p><strong>Solution:</strong> Use configuration files or environment variables to store values that might change. This improves security and allows for easy updates.</p>
<p><strong>6. Lack of Documentation and Testing:</strong></p>
<p>Documentation and testing are often neglected when time is short. But without proper documentation and test coverage, the code becomes challenging to understand and validate, slowing down development and increasing the risk of bugs.</p>
<p><strong>Solution:</strong> Implement test-driven development (TDD) or include time in the development cycle for creating documentation and writing tests. Aim for at least basic test coverage for critical paths and functions.</p>
<h3 id="heading-how-to-identify-and-manage-technical-debt">How to Identify and Manage Technical Debt</h3>
<p>Identifying technical debt is crucial if you want to address and improve it. Here are some strategies you can follow:</p>
<ol>
<li><p><strong>Code Reviews:</strong> Regular peer reviews help uncover areas of potential debt. In reviews, team members can flag complex code, lack of tests, or unclear logic, helping address these issues early.</p>
</li>
<li><p><strong>Automated Static Code Analysis:</strong> Tools like SonarQube, Code Climate, and ESLint (for JavaScript) analyze codebases for code smells, vulnerabilities, and complexity. They’re effective for spotting issues like duplicate code, long functions, and outdated dependencies.</p>
</li>
<li><p><strong>Regular Refactoring Sessions:</strong> Scheduling dedicated time for refactoring allows the team to improve existing code quality. During these sessions, focus on simplifying code, breaking down large functions, and removing duplicates.</p>
</li>
<li><p><strong>Technical Debt Backlog:</strong> Track technical debt items in a backlog, prioritizing them alongside feature development. This backlog helps balance feature work with debt reduction and keeps everyone aware of existing debt.</p>
</li>
</ol>
<h3 id="heading-how-to-deal-with-technical-debt-in-code">How to Deal with Technical Debt in Code</h3>
<p>Here’s a practical example to demonstrate how refactoring can help address technical debt, specifically by removing code duplication.</p>
<h4 id="heading-example-removing-duplicate-code">Example: Removing Duplicate Code</h4>
<p>Let’s say we have two functions that send different types of emails but use repeated code:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Duplicate code example</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_welcome_email</span>(<span class="hljs-params">user</span>):</span>
    send_email(user.email, <span class="hljs-string">"Welcome!"</span>, <span class="hljs-string">"Thanks for joining!"</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_password_reset_email</span>(<span class="hljs-params">user</span>):</span>
    send_email(user.email, <span class="hljs-string">"Password Reset"</span>, <span class="hljs-string">"Click here to reset your password."</span>)
</code></pre>
<p>Each function has a similar structure, so refactoring can make the code cleaner and reduce duplication.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Refactored code</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_email_to_user</span>(<span class="hljs-params">user, subject, message</span>):</span>
    send_email(user.email, subject, message)

<span class="hljs-comment"># Use the refactored function</span>
send_email_to_user(new_user, <span class="hljs-string">"Welcome!"</span>, <span class="hljs-string">"Thanks for joining!"</span>)
send_email_to_user(existing_user, <span class="hljs-string">"Password Reset"</span>, <span class="hljs-string">"Click here to reset your password."</span>)
</code></pre>
<p>This example demonstrates how consolidation can reduce repetition and make the code more flexible.</p>
<h3 id="heading-how-to-avoid-technical-debt">How to Avoid Technical Debt</h3>
<p>Proactively managing technical debt helps reduce it over time. Here are ways to avoid accumulating more debt:</p>
<ul>
<li><p><strong>Establish Code Standards:</strong> Create and enforce coding standards within the team. Consistent practices reduce complexity, improve readability, and make it easier to identify issues early.</p>
</li>
<li><p><strong>Refactor Regularly:</strong> Rather than waiting for debt to accumulate, make minor improvements during routine work. A “leave it better than you found it” approach ensures code quality remains high over time.</p>
</li>
<li><p><strong>Encourage Thorough Testing:</strong> Strong test coverage identifies potential problems early, reducing the likelihood of code with hidden issues. Testing tools like Jest for JavaScript or PyTest for Python make it easy to add tests to each function and module.</p>
</li>
<li><p><strong>Plan for Scalability:</strong> Think about future needs when designing code. Avoid shortcuts that might restrict scalability and performance as the application grows.</p>
</li>
<li><p><strong>Limit Workarounds and Temporary Fixes:</strong> If temporary fixes are necessary, document them and prioritize removing them as soon as possible. Keeping track of these “quick fixes” ensures they don’t become long-term issues.</p>
</li>
</ul>
<h2 id="heading-how-to-measure-code-quality-with-code-analysis-tools">How to Measure Code Quality with Code Analysis Tools</h2>
<p>Code quality tools can help you find issues that might not be obvious. They can point out things like unused variables, code that’s hard to read, or security problems. Popular tools include <code>ESLint</code> for <code>JavaScript</code>, <code>Pylint</code> for <code>Python</code>, and <code>SonarQube</code> for different programming languages.</p>
<p>Here’s how to set up a simple code check with ESLint:</p>
<ol>
<li><p><strong>Install ESLint</strong>:</p>
<pre><code class="lang-bash"> npm install eslint --save-dev
</code></pre>
</li>
<li><p><strong>Initialize ESLint</strong>:</p>
<pre><code class="lang-bash"> npx eslint --init
</code></pre>
<p> This command will prompt you to answer a few configuration questions. You can choose your preferred style guide and select a few options about your environment and file format.</p>
</li>
<li><p><strong>Example Code with Issues</strong></p>
<p> Here’s a sample JavaScript file (<code>example.js</code>) with a few common issues:</p>
<pre><code class="lang-javascript"> <span class="hljs-comment">// example.js</span>

 <span class="hljs-keyword">var</span> x = <span class="hljs-number">10</span>;   <span class="hljs-comment">// Unused variable</span>
 <span class="hljs-keyword">let</span> y = <span class="hljs-number">5</span>;
 <span class="hljs-keyword">const</span> z = <span class="hljs-string">'Hello World'</span>

 <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">calculateSum</span>(<span class="hljs-params">a, b</span>) </span>{
     <span class="hljs-keyword">return</span> a + b
 }

 calculateSum(<span class="hljs-number">3</span>, <span class="hljs-number">4</span>);

 <span class="hljs-comment">// Missing semicolon and inconsistent indentation</span>
 <span class="hljs-keyword">if</span>(y &gt; <span class="hljs-number">3</span>){
     <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Y is greater than 3"</span>)
 }
</code></pre>
</li>
<li><p><strong>Run ESLint</strong>:</p>
<pre><code class="lang-bash"> npx eslint example.js
</code></pre>
<p> After running this command, ESLint will analyze <code>example.js</code> and report any issues based on the configured rules.</p>
</li>
<li><p><strong>ESLint Output</strong></p>
<p> ESLint provides detailed feedback about the issues it detects:</p>
<pre><code class="lang-plaintext"> /path/to/example.js
   1:5  warning  'x' is assigned a value but never used          no-unused-vars
   3:12  error    Missing semicolon                               semi
   6:25  error    Missing semicolon                               semi
   10:1  error    Expected indentation of 4 spaces but found 3    indent
   11:26 error    Missing semicolon                               semi

 ✖ 5 problems (4 errors, 1 warning)
</code></pre>
<p> Here’s a breakdown of each issue detected by ESLint:</p>
<ul>
<li><p><strong>Unused Variable</strong>: ESLint identifies that <code>x</code> is declared but never used (<code>no-unused-vars</code> rule).</p>
</li>
<li><p><strong>Missing Semicolons</strong>: ESLint flags lines where semicolons are missing at the end of statements (<code>semi</code> rule).</p>
</li>
<li><p><strong>Inconsistent Indentation</strong>: ESLint notices that line 10 doesn’t follow consistent indentation (<code>indent</code> rule).</p>
</li>
</ul>
</li>
<li><p><strong>Fixing the Code</strong></p>
<p> Based on ESLint’s feedback, here’s the corrected code:</p>
<pre><code class="lang-javascript"> <span class="hljs-comment">// example.js</span>

 <span class="hljs-keyword">let</span> y = <span class="hljs-number">5</span>;
 <span class="hljs-keyword">const</span> z = <span class="hljs-string">'Hello World'</span>;

 <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">calculateSum</span>(<span class="hljs-params">a, b</span>) </span>{
     <span class="hljs-keyword">return</span> a + b;
 }

 calculateSum(<span class="hljs-number">3</span>, <span class="hljs-number">4</span>);

 <span class="hljs-keyword">if</span> (y &gt; <span class="hljs-number">3</span>) {
     <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Y is greater than 3"</span>);
 }
</code></pre>
<ul>
<li><p>We removed the unused variable <code>x</code>.</p>
</li>
<li><p>We added missing semicolons.</p>
</li>
<li><p>And we adjusted indentation for consistent spacing.</p>
</li>
</ul>
</li>
<li><p><strong>Re-run ESLint to Verify Fixes</strong></p>
<p> After making these changes, you can run <code>npx eslint example.js</code> again to confirm that there are no remaining issues. ESLint will return no output if everything is now clean, confirming that the code adheres to the configured standards.</p>
</li>
</ol>
<h3 id="heading-additional-tip-auto-fixing-with-eslint">Additional Tip: Auto-Fixing with ESLint</h3>
<p>ESLint can automatically fix some issues for you. To do this, use the <code>--fix</code> flag:</p>
<pre><code class="lang-bash">npx eslint example.js --fix
</code></pre>
<p>This command will automatically correct issues like indentation, unused variables, and missing semicolons where possible. But it’s important to review the changes to ensure they align with your intended functionality.</p>
<p>Reviewing code, spotting technical debt, and using quality tools help keep the codebase healthy. If you follow these steps, your project will be easier to manage and less likely to break.</p>
<h2 id="heading-ai-tools-to-help-you-improve-your-code">AI Tools to Help You Improve Your Code</h2>
<p>Using AI tools to restructure code makes improving code quality much faster and easier. These tools help find issues, suggest changes, and can even automate some parts of the refactoring process.</p>
<p>I'll share some AI tools that can help you with code analysis, refactoring, and dependency management, based on my own experience and what I've found useful.</p>
<h3 id="heading-best-ai-tools-for-code-restructuring">Best AI Tools for Code Restructuring</h3>
<p>AI-powered tools are becoming more common, and they offer different ways to boost code quality and simplify refactoring. Here are some I've found helpful:</p>
<h4 id="heading-1-github-copilot"><strong>1. GitHub Copilot</strong></h4>
<p>GitHub Copilot is like a coding assistant that provides smart suggestions as you write code. It can complete code snippets, suggest new functions, and help rework existing code to make it more efficient. I’ve found it useful for writing repetitive code blocks or coming up with quick refactorings.</p>
<p>For example, let’s say you need to rewrite a function to be more efficient:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Original function that checks if a number is prime</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">is_prime</span>(<span class="hljs-params">n</span>):</span>
    <span class="hljs-keyword">if</span> n &lt; <span class="hljs-number">2</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">2</span>, n):
        <span class="hljs-keyword">if</span> n % i == <span class="hljs-number">0</span>:
            <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>
</code></pre>
<p>GitHub Copilot might suggest optimizing the function like this:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Optimized version suggested by Copilot</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">is_prime</span>(<span class="hljs-params">n</span>):</span>
    <span class="hljs-keyword">if</span> n &lt; <span class="hljs-number">2</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">2</span>, int(n**<span class="hljs-number">0.5</span>) + <span class="hljs-number">1</span>):
        <span class="hljs-keyword">if</span> n % i == <span class="hljs-number">0</span>:
            <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>
</code></pre>
<p>The updated version checks factors only up to the square root of <code>n</code>, making it faster for large numbers.</p>
<h4 id="heading-2-qodogen"><strong>2. QodoGen</strong></h4>
<p>QodoGen provides automated suggestions for refactoring and can detect common code issues, like unused variables or large functions doing too many tasks. It also helps split complex code into smaller, more manageable pieces and can explain sections of the code base or the entire codebase which will facilitate the restructuring process.</p>
<p>This tool is capable of doing this because, unlike other AI assistants and general purpose code generation tools, Qodo focuses on code integrity, while generating tests that help you understand how your code behaves. This can help you discover edge cases and suspicious behaviors, and make your code more robust.</p>
<p>For example, if you have a function handling multiple tasks, QodoGen might suggest breaking it down:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Before refactoring</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_user_data</span>(<span class="hljs-params">user_data</span>):</span>
    validate_data(user_data)
    save_to_database(user_data)
    send_welcome_email(user_data)

<span class="hljs-comment"># After refactoring</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_user_data</span>(<span class="hljs-params">user_data</span>):</span>
    validated_data = validate_data(user_data)
    save_data(validated_data)
    notify_user(validated_data)
</code></pre>
<p>Separating the steps makes the code easier to maintain and test.</p>
<h4 id="heading-3-chatgpt-for-code-assistance"><strong>3. ChatGPT for Code Assistance</strong></h4>
<p>ChatGPT can act as a helpful companion when working on code restructuring tasks. Arguably the most used coding assistant, it provides advice on refactoring strategies, explains how to implement changes, or offers example snippets. It’s like having an expert to consult whenever you need guidance or ideas.</p>
<p>For instance, if you’re unsure how to optimize a function or restructure a class, ChatGPT can provide sample code or describe best practices. You can also ask it for help with understanding errors or fixing specific problems in your code.</p>
<p>Just make sure you double-check the code it provides (same goes for all these AI assistants) as it can hallucinate and make mistakes.</p>
<h3 id="heading-automated-tools-for-refactoring-and-analysis">Automated Tools for Refactoring and Analysis</h3>
<p>AI tools not only assist with writing code but also with analyzing it for quality improvements:</p>
<h4 id="heading-1-sonarqube"><strong>1. SonarQube</strong></h4>
<p>SonarQube scans the code to detect bugs, vulnerabilities, and code smells. It generates reports with suggestions on what to fix, helping maintain a healthy codebase.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Sample SonarQube configuration</span>
sonar.projectKey=my_project
sonar.sources=src
sonar.host.url=http://localhost:9000
sonar.login=my_token
</code></pre>
<h4 id="heading-2-resharper"><strong>2. ReSharper</strong></h4>
<p>This tool integrates with Visual Studio and offers automatic refactoring options. It highlights code that can be simplified or cleaned up and suggests ways to optimize the codebase.</p>
<h4 id="heading-3-depcheck-for-dependency-management"><strong>3. DepCheck for Dependency Management</strong></h4>
<p>AI tools like DepCheck help find unused dependencies in JavaScript projects, keeping package files clean.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Running DepCheck to find unused dependencies</span>
npx depcheck
</code></pre>
<h3 id="heading-how-these-tools-help-with-code-restructuring">How These Tools Help with Code Restructuring</h3>
<p>Using AI tools like GitHub Copilot, QodoGen, and ChatGPT speeds up the process of code restructuring. They provide suggestions that save time and catch issues early, making the code easier to maintain.</p>
<p>Combining these tools with automated analysis tools like SonarQube and ReSharper ensures all aspects of the codebase are covered, from quality checks to refactoring.</p>
<p>These AI tools have other features that facilitate this process: for example, they all have a chat feature that lets you ask questions and get replies about your code and any best practices you should be following. Also, QodoGen allows you to add parts of or the whole codebase for context with the click of a button, along with other features for test generation and pull request reviews.</p>
<p>When restructuring your codebase, having a variety of AI tools can make the process smoother and more efficient. This is AI usage at its best.</p>
<h2 id="heading-version-control-best-practices-for-code-changes">Version Control Best Practices for Code Changes</h2>
<p>Version control keeps track of code changes, making it easier to manage updates, collaborate with others, and fix issues. Following some best practices can help maintain a clean and organized codebase.</p>
<p>Let’s look at how to manage code changes, track updates, and ensure quality through code reviews.</p>
<h3 id="heading-using-git-branching-strategies-to-manage-code-changes">Using Git Branching Strategies to Manage Code Changes</h3>
<p>Git branching helps keep different versions of the code separate, allowing multiple developers to work without affecting the main codebase. Here are some common strategies:</p>
<h4 id="heading-1-feature-branching"><strong>1. Feature Branching</strong></h4>
<p>Feature branches allow developers to work on a new feature without changing the main codebase. Each feature gets its own branch, and once complete, it can be merged into the main branch.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Creating a new feature branch</span>
git checkout -b feature/new-login-page

<span class="hljs-comment"># Working on the new feature and then committing changes</span>
git add .
git commit -m <span class="hljs-string">"Added login page UI"</span>

<span class="hljs-comment"># Merging the feature branch into the main branch</span>
git checkout main
git merge feature/new-login-page
</code></pre>
<h4 id="heading-2-gitflow-strategy"><strong>2. GitFlow Strategy</strong></h4>
<p>This strategy involves using multiple branches for different stages of development, such as feature, develop, and release. It separates development work and allows smoother integration and deployment.</p>
<ul>
<li><p><strong>Main Branch</strong>: Contains production-ready code.</p>
</li>
<li><p><strong>Develop Branch</strong>: Holds the latest completed work, ready for the next release.</p>
</li>
<li><p><strong>Feature Branches</strong>: Created from the develop branch for new features.</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Switch to the develop branch</span>
git checkout develop

<span class="hljs-comment"># Create a new branch for a feature</span>
git checkout -b feature/upgrade-search

<span class="hljs-comment"># Commit changes and push the feature branch</span>
git add .
git commit -m <span class="hljs-string">"Improved search feature"</span>
git push origin feature/upgrade-search
</code></pre>
<h3 id="heading-how-to-track-and-document-code-updates">How to Track and Document Code Updates</h3>
<p>Documenting code changes helps keep the team informed and makes it easier to understand what was done later. Here are some tips for tracking updates:</p>
<h4 id="heading-1-writing-clear-commit-messages"><strong>1. Writing Clear Commit Messages</strong></h4>
<p>Commit messages should explain what was changed and why. A clear message helps others know the purpose of each update.</p>
<p>Example:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Good commit message</span>
git commit -m <span class="hljs-string">"Fixed bug that caused login failure on mobile devices"</span>

<span class="hljs-comment"># Bad commit message</span>
git commit -m <span class="hljs-string">"Fixed bug"</span>
</code></pre>
<h4 id="heading-2-using-tags-to-mark-releases"><strong>2. Using Tags to Mark Releases</strong></h4>
<p>Tags can be used to label important points in the project’s history, such as release versions. This makes it easier to find stable versions of the code.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create a tag for version 1.0</span>
git tag v1.0

<span class="hljs-comment"># Push the tag to the remote repository</span>
git push origin v1.0
</code></pre>
<h4 id="heading-3-creating-and-using-changelogs"><strong>3. Creating and Using Changelogs</strong></h4>
<p>A changelog lists the changes made in each version, helping developers and users see what was updated or fixed.</p>
<p>Example format for a changelog:</p>
<pre><code class="lang-plaintext">## [1.0.1] - 2024-10-01
### Added
- New login feature

### Fixed
- Resolved search issue on homepage

### Changed
- Updated user dashboard layout
</code></pre>
<h3 id="heading-importance-of-code-reviews-in-maintaining-code-quality">Importance of Code Reviews in Maintaining Code Quality</h3>
<p>Code reviews help catch errors, share knowledge, and ensure code stays clean and maintainable. Here are some practices to follow for effective code reviews:</p>
<h4 id="heading-1-keep-code-changes-small"><strong>1. Keep Code Changes Small</strong></h4>
<p>Smaller changes are easier to review, making it more likely to spot mistakes. Large changes can be broken down into smaller parts.</p>
<h4 id="heading-2-use-pull-requests-for-reviews"><strong>2. Use Pull Requests for Reviews</strong></h4>
<p>Pull requests create a space for discussion around changes. Team members can review the changes, suggest improvements, and approve the updates.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Push the feature branch to the remote repository</span>
git push origin feature/new-feature

<span class="hljs-comment"># Create a pull request on GitHub, GitLab, or Bitbucket</span>
</code></pre>
<h4 id="heading-3-provide-constructive-feedback"><strong>3. Provide Constructive Feedback</strong></h4>
<p>Code reviews should aim to improve the code without discouraging the developer. Suggest better ways to solve problems and explain the reasoning.</p>
<p>Example comments during a code review:</p>
<ul>
<li><p>"Consider using a list instead of a dictionary for this data structure, as it simplifies the code."</p>
</li>
<li><p>"This function is doing multiple tasks. It might be clearer if we split it into two separate functions."</p>
</li>
</ul>
<p>Using these practices helps ensure code changes are managed effectively, updates are well-documented, and the quality of the codebase remains high. Regular code reviews and proper branching strategies make it easier for teams to collaborate and keep the project on track.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Reviving and restructuring a codebase can seem like a big task, but taking small, planned steps makes it manageable. Start by checking the current state of the code and making a list of areas that need work. Set clear goals and create a plan to improve the code, step by step.</p>
<p>Using the tools we discussed here can help find issues, suggest changes, and even automate some tasks. Version control practices, such as branching strategies and code reviews, keep changes organized and ensure the quality stays high.</p>
<p>With a solid approach, even the messiest codebase can become clean, efficient, and easier to work with.</p>
<h3 id="heading-resources">Resources</h3>
<ul>
<li><p>AI tools have been developed to assist with the Git branching, Pull Request reviews and approval. Check out <a target="_blank" href="https://dev.to/oluwadamisisamuel1/merge-mastery-elevating-your-pull-request-game-in-open-source-projects-25fo">this article</a> to read more on one of my favorites.</p>
</li>
<li><p>If you want a step by step tutorial on how to revive and refactor your code, check out <a target="_blank" href="https://youtu.be/yMQJUaUtiJo?si=CGd2WBcD117p7lrS">this youtube video</a>.</p>
</li>
<li><p>Check out <a target="_blank" href="https://www.freecodecamp.org/news/best-practices-for-refactoring-code/">this freecodecamp article</a> on code restructuring to dive deeper.</p>
</li>
</ul>
<p>Connect with me on <a target="_blank" href="https://www.linkedin.com/in/samuel-oluwadamisi-01b3a4236/?lipi=urn%3Ali%3Apage%3Ad_flagship3_feed%3BxAUJMbSgQTeDtb7n2d0mQQ%3D%3D">LinkedIn</a>, <a target="_blank" href="https://twitter.com/Data_Steve_">Twitter</a>, and <a target="_blank" href="https://dev.to/dashboard">my peronal blog</a> if you found this helpful.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How AI Tools Can Help You Reuse Code ]]>
                </title>
                <description>
                    <![CDATA[ Reusing code is an important part of software development. Instead of writing the same code again and again, developers can save time and effort by using code that already works. This not only speeds up the development process but also helps improve ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/tools-for-code-reuse/</link>
                <guid isPermaLink="false">66f46fb236ee3dad69878324</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Programming Tips ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ learning ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwadamisi Samuel ]]>
                </dc:creator>
                <pubDate>Wed, 25 Sep 2024 20:16:50 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727102263215/bcd245a1-4e4e-4563-8f6c-1715b5f8ed13.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Reusing code is an important part of software development. Instead of writing the same code again and again, developers can save time and effort by using code that already works. This not only speeds up the development process but also helps improve the quality of the final product.</p>
<p>There are several popular ways to create reusable code, and each has its strengths and weaknesses. But they all share a common goal: making it easier for you to avoid reinventing the wheel.</p>
<p>And now, with the improvements in AI, you can take advantage of the available technology to make the development process easier, faster, and more efficient.</p>
<p>In this article, we’ll explore the benefits of code reuse, discuss popular tools for finding reusable code (which, in my opinion, are the best tools to use), and examine how different solutions can help you work more efficiently.</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-the-benefits-of-code-reuse">Benefits of Code Reuse</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-popular-options-for-code-reuse">Popular Options for Code Reuse</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-the-benefits-of-code-reuse">The Benefits of Code Reuse</h2>
<p>Reusing code brings many advantages that make software development faster and better. Using code that already works helps you avoid doing the same tasks again and again. It also helps you follow the “DRY” principle, and lets you focus on more important parts of your projects.</p>
<h3 id="heading-time-savings-and-faster-development-cycles">Time Savings and Faster Development Cycles</h3>
<p>A key benefit of reusing code is the amount of time it saves. You no longer need to spend hours rewriting common functions or tasks that have already been solved. This allows you to quickly add these pieces to new projects and spend more time building new features that make your product unique.</p>
<h3 id="heading-improved-code-quality">Improved Code Quality</h3>
<p>Code that has already been tested and proven tends to have fewer mistakes. Using reusable code that has been checked before means you can trust that it will work properly, reducing the chances of bugs and errors in the final product.</p>
<h3 id="heading-increased-consistency">Increased Consistency</h3>
<p>Reusing code helps create consistency across different projects. The same functions and logic are applied in every project, ensuring everything works in a similar way. This makes the codebase easier to understand and maintain.</p>
<h3 id="heading-enhanced-collaboration-and-knowledge-sharing">Enhanced Collaboration and Knowledge Sharing</h3>
<p>When teams share and reuse components, collaboration becomes easier. Team members can work together more efficiently, using familiar code pieces. This also helps spread knowledge across the team, as everyone benefits from understanding and using the same code.</p>
<h3 id="heading-cost-efficiency">Cost Efficiency</h3>
<p>Reusing existing solutions helps reduce costs. Since you spend less time creating new code from scratch, the development process becomes cheaper. This can also lead to faster project completion, which saves money in the long run.</p>
<h2 id="heading-popular-options-for-code-reuse">Popular Options for Code Reuse</h2>
<p>There are several tools and platforms that developers rely on to find reuseable code. There are widely used options like Google, StackOverflow, and documentation. And on the other hand there are newer AI tools like ChatGPT, Gemini, and Codiumate to mention a few.</p>
<p>Each option has its own strengths and weaknesses, but they all aim to make coding easier and faster by offering pre-existing solutions. Below, we’ll look at some of the most popular options and compare how they help with code reuse.</p>
<h3 id="heading-google-search">Google Search</h3>
<p>Google is one of the most widely-used tools for finding code snippets and tutorials. Developers often search for specific solutions, reading through blog posts, forums, and official documentation to find what they need.</p>
<p><strong>Advantages</strong>:</p>
<ul>
<li><p>Access to a vast amount of information, tutorials, and resources across the web.</p>
</li>
<li><p>Great for finding niche solutions or specific examples.</p>
</li>
<li><p>Helpful for discovering documentation and learning new technologies.</p>
</li>
</ul>
<p><strong>Disadvantages</strong>:</p>
<ul>
<li><p>Finding the right solution often involves sifting through a lot of content.</p>
</li>
<li><p>Not all resources are trustworthy or up-to-date.</p>
</li>
<li><p>It can take longer to find specific, reusable code compared to other platforms.</p>
</li>
</ul>
<p><strong>Google in Action</strong>: A quick goggle search will show results comprised of articles and publications related to your question. Going through these articles might help with the exact code you are searching for.</p>
<h3 id="heading-stack-overflow">Stack Overflow</h3>
<p>Stack Overflow is a popular online forum where developers ask questions and share solutions to programming problems, and it’s been a reliable friend to developers over the years.</p>
<p>It has a massive library of answers and code snippets provided by the developer community. It also now has an AI feature which we’ll discuss in the next section. You get access to different solutions to the same problem provided by developers.</p>
<p><strong>Advantages</strong>:</p>
<ul>
<li><p>Large collection of real-world code examples and solutions.</p>
</li>
<li><p>Answers are often reviewed and voted on by other users, making it easier to find high-quality code.</p>
</li>
<li><p>Covers a wide range of coding topics and issues.</p>
</li>
</ul>
<p><strong>Disadvantages</strong>:</p>
<ul>
<li><p>Requires manual searching, which can be time-consuming.</p>
</li>
<li><p>Some solutions may be outdated or not relevant to modern development practices.</p>
</li>
<li><p>It’s not always easy to find code that fits perfectly with your specific project needs.</p>
</li>
</ul>
<p><strong>StackOverflow in Action</strong>: You can just open up the site in your browser and search what ever question you need and the highest rated answers will be provided:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727287523667/2d476910-327a-42b2-a52f-6247e3b0e1f9.png" alt="StackOverflow search results example" class="image--center mx-auto" width="1856" height="1320" loading="lazy"></p>
<h3 id="heading-multi-purpose-ai-tools-like-chatgpt-and-gemini">Multi-Purpose AI Tools (like ChatGPT and Gemini)</h3>
<p>ChatGPT and Gemini are AI tools that help developers by providing code snippets and answering programming questions. They are known for their versatility, supporting a wide range of programming languages and offering quick suggestions on how to solve coding problems.</p>
<p>These tools are growing each day and have truly revolutionised the tech space. They can provide code snippets quickly and with sources.</p>
<p><strong>Advantages</strong>:</p>
<ul>
<li><p>Can generate code for various languages and frameworks.</p>
</li>
<li><p>Provides quick explanations and examples and sources.</p>
</li>
<li><p>Easy to use for beginners and experienced developers alike.</p>
</li>
</ul>
<p><strong>Disadvantages</strong>:</p>
<ul>
<li><p>These AI toools are general-purpose, so they may not always provide the most optimized or accurate code for complex tasks and sometimes struggle with context.</p>
</li>
<li><p>Since they’re not focused solely on coding, they might miss important details or suggest outdated solutions.</p>
</li>
</ul>
<p><strong>ChatGPT in Action</strong>: Let’s see ChatGPT in action with a simple question and the sample code it gives us:</p>
<p><strong>Question:</strong> “Can you generate a function to validate form inputs in JavaScript?"</p>
<p><strong>Response:</strong> Here’s a reusable JavaScript function to validate form inputs. This function will validate if inputs are not empty, and optionally check for conditions such as valid email addresses, minimum character length, and numeric validation. You can expand it depending on the form requirements.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">validateForm</span>(<span class="hljs-params">inputs</span>) </span>{
  <span class="hljs-keyword">let</span> errors = {};

  inputs.forEach(<span class="hljs-function"><span class="hljs-params">input</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> { name, value, type, required, minLength, pattern } = input;

    <span class="hljs-comment">// Check if the input is required and empty</span>
    <span class="hljs-keyword">if</span> (required &amp;&amp; !value) {
      errors[name] = <span class="hljs-string">`<span class="hljs-subst">${name}</span> is required`</span>;
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// Check for minimum length</span>
    <span class="hljs-keyword">if</span> (minLength &amp;&amp; value.length &lt; minLength) {
      errors[name] = <span class="hljs-string">`<span class="hljs-subst">${name}</span> must be at least <span class="hljs-subst">${minLength}</span> characters`</span>;
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// Check for valid email format</span>
    <span class="hljs-keyword">if</span> (type === <span class="hljs-string">'email'</span> &amp;&amp; !<span class="hljs-regexp">/\S+@\S+\.\S+/</span>.test(value)) {
      errors[name] = <span class="hljs-string">'Invalid email address'</span>;
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// Check if the input matches the specified pattern (e.g., phone number)</span>
    <span class="hljs-keyword">if</span> (pattern &amp;&amp; !<span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(pattern).test(value)) {
      errors[name] = <span class="hljs-string">`<span class="hljs-subst">${name}</span> is invalid`</span>;
      <span class="hljs-keyword">return</span>;
    }
  });

  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.keys(errors).length ? errors : <span class="hljs-literal">true</span>;
}

<span class="hljs-comment">// Example usage:</span>
<span class="hljs-keyword">const</span> inputs = [
  { <span class="hljs-attr">name</span>: <span class="hljs-string">'username'</span>, <span class="hljs-attr">value</span>: <span class="hljs-string">'john_doe'</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">minLength</span>: <span class="hljs-number">5</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">'email'</span>, <span class="hljs-attr">value</span>: <span class="hljs-string">'john@example'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'email'</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">'password'</span>, <span class="hljs-attr">value</span>: <span class="hljs-string">'1234'</span>, <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">minLength</span>: <span class="hljs-number">6</span> },
];

<span class="hljs-keyword">const</span> validation = validateForm(inputs);
<span class="hljs-built_in">console</span>.log(validation);
</code></pre>
<p>This response will save you the trouble of thinking about the logic and still gives space for customization. Just make sure to test the code to make sure it works for your purposes.</p>
<h3 id="heading-overflow-ai-from-stack-overflow">Overflow AI (from Stack Overflow)</h3>
<p>Overflow AI is a generative AI tool integrated into the Stack Overflow platform where users can ask questions in natural language and receive summarized answers. The answers include proper citations for existing content from Stack Overflow’s massive library.</p>
<p>Overflow AI can provide code snippets and solutions to your problems using the highest rated answers. Just keep in mind that there doesn’t appear to be a free version at the moment, so you may have to pay for the service.</p>
<p><strong>Advantages</strong>:</p>
<ul>
<li><p>Access to Stack Overflow’s large collection of real-world code examples and solutions.</p>
</li>
<li><p>Answers provided are the highest voted answers to your specific question.</p>
</li>
<li><p>Code provided is usually of the highest quality.</p>
</li>
<li><p>It can be integrated into your IDE and Slack platform for teams.</p>
</li>
</ul>
<p><strong>Disadvantages</strong>:</p>
<ul>
<li><p>Some answers may not suit your specific needs for more complex problems or code.</p>
</li>
<li><p>It struggles with context as other multi-purpose AI tools do as there is no way to train it specifically for your project.</p>
</li>
</ul>
<p><strong>OverFlow AI in Action</strong>: OverFlow AI is currently only available for Stack Overflow for Teams Enterprise. Companies are required to have a subscription for their teams.</p>
<h3 id="heading-codiumate">Codiumate</h3>
<p>Codiumate is an open-source AI-powered coding assistant designed specifically to enhance the software development process. It’s trained solely for the purpose of assisting developers by providing high quality and reusable code, iterative tests, PR reviews, and code completion.</p>
<p>It also has a chat feature which lets you ask questions. But the most impressive feature it has, in my view, is the option to input your entire codebase or chunks of it so that it has context for the code you need from it.</p>
<p>When it comes to reusing code effectively, having a tool specifically designed for that purpose makes all the difference. And it’s great that Codiumate is an open-source tool, too.</p>
<h4 id="heading-advantages">Advantages:</h4>
<ul>
<li><p>Codiumate is specialized. While other tools like ChatGPT are useful for many tasks, Codiumate’s focus allows it to deliver more precise and relevant recommendations for code reuse.</p>
</li>
<li><p>It can scan large codebases quickly, identifying opportunities to reuse code, and offering seamless integration options.</p>
</li>
<li><p>It possesses features like automatic test creation and real-time bug detection.</p>
</li>
<li><p>It has smart code completion, a coding assistant, and a PR-agent for Pull Request reviews(chrome extension) which makes it a one-stop shop.</p>
</li>
</ul>
<h4 id="heading-limitations">Limitations:</h4>
<ul>
<li><p>As an open-source tool, Codiumate may require some initial setup or customization to fit your specific development environments. This can take a little extra time upfront, especially for developers who aren't familiar with the tool. But once it's integrated, the time savings and improvements to code quality make it well worth the effort. <a target="_blank" href="https://codiumate-docs.codium.ai/installation/">You can find the link to setup here.</a></p>
</li>
<li><p>Since Codiumate is designed specifically for code reuse, it may not be the best tool for handling broader queries outside the realm of coding. If you’re looking for more general advice or help with non-code-related tasks, you might still need to use other tools like ChatGPT. But for focused, efficient code reuse, Codiumate is a great choice.</p>
</li>
</ul>
<p><strong>Codiumate in Action</strong>: Let’s see Codiumate in action:</p>
<p>After setting Codiumate up (which takes about 2 minutes), you can access the chat feature where you can add the entire codebase or highlight part of it for context to get more specific results:</p>
<p>Question: “Can you generate a function to validate form inputs in JavaScript?"</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727285759800/4ff8044f-30b6-49a7-a9ef-ba264f215f33.png" alt="4ff8044f-30b6-49a7-a9ef-ba264f215f33" class="image--center mx-auto" width="994" height="224" loading="lazy"></p>
<p>Response:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727285642041/d56681a4-94f0-410d-ac4d-b4706bc6c363.gif" alt="Codiumate generating the code to validate inputs in JavaScript" class="image--center mx-auto" width="1152" height="648" loading="lazy"></p>
<p>You can expand this function to include more validations as needed, such as checking for special characters in the password, matching passwords, and so on by clicking the “continue this chat” button.</p>
<h3 id="heading-additional-resources">Additional Resources</h3>
<p>There are a ton of AI tools and platforms available with similar features and we’re almost spoiled for choice at this point.</p>
<p>Here are a few additional resources about some of my favorite tools I discussed:</p>
<ul>
<li><p>Read more on <a target="_blank" href="https://codiumate-docs.codium.ai/">Codiumate</a></p>
</li>
<li><p><a target="_blank" href="https://stackoverflow.co/teams/resources/introducing-overflowai/">Read more on OverflowAI</a></p>
</li>
<li><p><a target="_blank" href="https://youtu.be/ui5SdYR7Ivs?si=UEyIeg1UtsXxkk2O">How to reuse code with codiumate (Youtube)</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/how-to-use-ai-to-improve-code-quality/">Using AI to improve code quality</a></p>
</li>
</ul>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Reusing code is a useful practice that makes development faster and easier. It allows developers to focus on creating new features while keeping their projects consistent. Reusing code also helps teams work together better and share knowledge more easily.</p>
<p>There are a ton of AI platforms available like to help with code reuse, and each has its own benefits (and I haven’t covered them all here.</p>
<p>You should take advantage of these AI tools and make your choice based on which tools suits you more. Codiumate and OverflowAI stand out above the rest to me, but the right tool will depend on what you need at that moment.</p>
<p>In the end, AI tools are there to make the development process more streamlined, cheaper, and to ultimately make your life easier as a developer.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ React Optimization Techniques to Help You Write More Performant Code ]]>
                </title>
                <description>
                    <![CDATA[ Performance optimization is a critical aspect of developing web applications. Users expect applications to load quickly and respond to their interactions smoothly.  In the React ecosystem, performance optimization techniques can significantly enhance... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/react-performance-optimization-techniques/</link>
                <guid isPermaLink="false">66bf4b68be10d17622ff3a0c</guid>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Temitope Oyedele ]]>
                </dc:creator>
                <pubDate>Fri, 16 Feb 2024 00:57:28 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/02/pexels-howard-adams-575835--1-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Performance optimization is a critical aspect of developing web applications. Users expect applications to load quickly and respond to their interactions smoothly. </p>
<p>In the React ecosystem, performance optimization techniques can significantly enhance the user experience by reducing load times and improving responsiveness.</p>
<p>In this article, we will discuss eight effective techniques for optimizing the performance of your React application.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#heading-why-performance-optimization-is-important">Why Performance Optimization is Important</a></li>
<li><a class="post-section-overview" href="#heading-list-visualization">List visualization</a></li>
<li><a class="post-section-overview" href="#heading-lazy-loading-images">Lazy Loading Images</a></li>
<li><a class="post-section-overview" href="#heading-memoization">Memoization</a></li>
<li><a class="post-section-overview" href="#heading-throttling-and-debouncing-events">Throttling and Debouncing Events</a></li>
<li><a class="post-section-overview" href="#heading-code-splitting">Code Splitting</a></li>
<li><a class="post-section-overview" href="#heading-react-fragments">React Fragments</a></li>
<li><a class="post-section-overview" href="#heading-web-workers">Web Workers</a></li>
<li><a class="post-section-overview" href="#heading-usetransition-hook">UseTransition Hook</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ol>
<h2 id="heading-why-performance-optimization-is-important">Why Performance Optimization is Important</h2>
<p>Optimizing the performance of your React application is crucial for several reasons:</p>
<ul>
<li><strong>Better User Experience:</strong> A slow-loading or laggy application can lead to a poor user experience, negatively impacting your business. Users expect fast and responsive interactions, and performance optimization helps deliver that.</li>
<li><strong>Improved SEO:</strong> Search engines like Google consider page load times and overall performance when ranking websites. A well-optimized application will rank higher in search results, making it more visible to potential users.</li>
<li><strong>Reduced Bounce Rates:</strong> If your application takes too long to load or respond, users will likely leave and never return. By optimizing performance, you can reduce bounce rates and increase engagement.</li>
<li><strong>Cost Savings</strong> A performant application requires fewer resources (like servers and memory) to handle the same workload. This means lower hosting costs and reduced infrastructure needs.</li>
<li><strong>Competitive Advantage:</strong> A fast and efficient application sets you apart from competitors whose applications may be slower or less optimized. According to research by <a target="_blank" href="https://www.portent.com/blog/analytics/research-site-speed-hurting-everyones-revenue.htm">Portent</a>, a website that loads within one second has a conversion rate five times higher than a site that takes ten seconds to load. Therefore, ensuring your React applications perform well is crucial for retaining users and maintaining a competitive edge.</li>
</ul>
<h2 id="heading-8-react-performance-optimization-techniques">8 React Performance Optimization Techniques</h2>
<p>Below are eight React performance optimization techniques you can use to speed up your applications.</p>
<h3 id="heading-list-visualization">List visualization</h3>
<p>List visualization, or windowing, involves rendering only the items currently visible on the screen. </p>
<p>When dealing with a large number of items in a list, rendering all the items at once can lead to slow performance and consume a significant amount of memory. List virtualization tackles this issue by rendering only a subset of the list items currently visible within the view, which conserves resources as the users scroll through the list.</p>
<p>The virtualization technique dynamically replaces rendered items with new ones, keeping the visible portion of the list updated and responsive. It efficiently allows you to render large lists or tabular data by only rendering the visible portion, recycling components as needed, and optimizing scroll performance.</p>
<p>There are different approaches to implementing list visualization in React, and one is using a popular library called <a target="_blank" href="https://www.npmjs.com/package/react-virtualized">React Virtualized</a>. </p>
<p>To install <code>react-virtualized</code>, you can use the following command:</p>
<pre><code class="lang-bash">npm install react-virtualized --save
</code></pre>
<p>After installing <code>react-virtualized</code>, you can import the required components and styles. Below is an example of how to use the <code>List</code> component to create a virtualized list:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { List } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-virtualized'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'react-virtualized/styles.css'</span>; <span class="hljs-comment">// Import styles</span>

<span class="hljs-comment">// Your list data</span>
<span class="hljs-keyword">const</span> list = <span class="hljs-built_in">Array</span>(<span class="hljs-number">5000</span>).fill().map(<span class="hljs-function">(<span class="hljs-params">_, index</span>) =&gt;</span> ({
  <span class="hljs-attr">id</span>: index,
  <span class="hljs-attr">name</span>: <span class="hljs-string">`Item <span class="hljs-subst">${index}</span>`</span>
}));

<span class="hljs-comment">// Function to render each row</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rowRenderer</span>(<span class="hljs-params">{ index, key, style }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{key}</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{style}</span>&gt;</span>
      {list[index].name}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-comment">// Main component</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyVirtualizedList</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">List</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">{300}</span>
      <span class="hljs-attr">height</span>=<span class="hljs-string">{300}</span>
      <span class="hljs-attr">rowCount</span>=<span class="hljs-string">{list.length}</span>
      <span class="hljs-attr">rowHeight</span>=<span class="hljs-string">{20}</span>
      <span class="hljs-attr">rowRenderer</span>=<span class="hljs-string">{rowRenderer}</span>
    /&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyVirtualizedList;
</code></pre>
<p>In this example, <code>List</code> is the main component provided by <code>react-virtualized</code>. The <code>rowRenderer</code> function defines how each row should be rendered. The <code>width</code>, <code>height</code>, <code>rowCount</code>, <code>rowHeight</code>, and <code>rowRenderer</code> props are essential for configuring the list's behavior and appearance. </p>
<p>React applications can handle massive amounts of data by leveraging list virtualization without sacrificing performance or user experience.</p>
<h3 id="heading-lazy-loading-images">Lazy Loading Images</h3>
<p>Similar to the list virtualization technique, lazy loading images prevents the creation of unnecessary DOM nodes, thereby boosting performance. Lazy loading allows you to defer or delay the loading of images until they are needed or visible to the user instead of loading all the images on page load.</p>
<p>The concept behind lazy loading is to initiate the load of a placeholder or a small low-resolution version of the image, typically a small-sized thumbnail or a blurred placeholder. As the user scrolls or interacts with the page, the actual image is loaded dynamically, replacing the placeholder when the user enters the viewport or when it becomes visible.</p>
<p>Lazy loading in React can be achieved using various libraries and techniques. One of the popular libraries is the <a target="_blank" href="https://www.npmjs.com/package/react-lazyload">react-lazyload</a>.  </p>
<p>To install <code>react-lazyload</code>, you can use the following command:</p>
<pre><code class="lang-bash">npm install --save react-lazyload
</code></pre>
<p>Below is an example of a simple React component that uses <code>react-lazyload</code> to implement lazy loading for images:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> LazyLoad <span class="hljs-keyword">from</span> <span class="hljs-string">'react-lazyload'</span>;

<span class="hljs-keyword">const</span> MyLazyLoadedImage = <span class="hljs-function">(<span class="hljs-params">{ src, alt }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">LazyLoad</span> <span class="hljs-attr">height</span>=<span class="hljs-string">{200}</span> <span class="hljs-attr">offset</span>=<span class="hljs-string">{100}</span>&gt;</span>
      {/* The height and offset props control when the image should start loading */}
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{src}</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">{alt}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">LazyLoad</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyLazyLoadedImage;
</code></pre>
<p>In this example, <code>MyLazyLoadedImage</code> uses the <code>LazyLoad</code> component from <code>react-lazyload</code>. The <code>height</code> prop specifies the height of the placeholder, and the <code>offset</code> prop determines how far below the viewport the placeholder should start loading.</p>
<p>Another approach is to use the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">intersection observer API</a>, which is a web API that allows you to detect when an element enters or exists the viewport efficiently. Here's how we can use the Intersection Observer API along with the <code>useEffect</code> hook in React:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useEffect, useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> IntersectionLazyLoad = <span class="hljs-function">(<span class="hljs-params">{ src, alt }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> imageRef = useRef();

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> options = {
      <span class="hljs-attr">root</span>: <span class="hljs-literal">null</span>, <span class="hljs-comment">// Use the viewport as the root</span>
      <span class="hljs-attr">rootMargin</span>: <span class="hljs-string">'0px'</span>, <span class="hljs-comment">// No margin around the root</span>
      <span class="hljs-attr">threshold</span>: <span class="hljs-number">0.5</span>, <span class="hljs-comment">// 50% of the image should be visible</span>
    };

    <span class="hljs-keyword">const</span> observer = <span class="hljs-keyword">new</span> IntersectionObserver(handleIntersection, options);

    <span class="hljs-keyword">if</span> (imageRef.current) {
      observer.observe(imageRef.current);
    }

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Cleanup the observer when the component is unmounted</span>
      observer.disconnect();
    };
  }, []);

  <span class="hljs-keyword">const</span> handleIntersection = <span class="hljs-function">(<span class="hljs-params">entries</span>) =&gt;</span> {
    entries.forEach(<span class="hljs-function">(<span class="hljs-params">entry</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (entry.isIntersecting) {
        <span class="hljs-comment">// Load the image when it becomes visible</span>
        imageRef.current.src = src;
        imageRef.current.alt = alt;
      }
    });
  };

  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{imageRef}</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">height:</span> '<span class="hljs-attr">200px</span>' }} <span class="hljs-attr">alt</span>=<span class="hljs-string">"Placeholder"</span> /&gt;</span></span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> IntersectionLazyLoad;
</code></pre>
<p>In this example, <code>IntersectionLazyLoad</code> uses the Intersection Observer API to determine when the image becomes visible in the viewport. </p>
<p>By utilizing this API along with React <code>useEffect</code> hook, you can implement your custom lazy loading solution for images in React.</p>
<h3 id="heading-memoization">Memoization</h3>
<p>Memoization in React is a technique used to optimize the performance of functional components by caching the results of expensive computations or function calls. It's particularly useful when dealing with computationally intensive or frequently called functions with the same input values, as it helps avoid redundant calculations and improves the overall efficiency of the application.</p>
<p>In React, there are three techniques for memoization: <code>React.memo()</code>, <code>useMemo(),</code> and <code>useCallback().</code> Let's delve into the details for each:</p>
<h4 id="heading-how-to-use-reactmemo">How to use <code>React.memo()</code></h4>
<p>This higher-order component wraps purely functional components to prevent re-rendering if the received props remain unchanged.</p>
<p>By using <code>React.memo()</code>, the rendering result is cached based on props. If the props haven't changed since the last render, React reuses the previously rendered result instead of redoing the rendering process. This saves time and resources.</p>
<p> Below is an example on how to use the <code>React.memo</code> with a functional component:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> Post = <span class="hljs-function">(<span class="hljs-params">{ signedIn, post }</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Rendering Post'</span>);
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>{post.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{post.content}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      {signedIn &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Edit Post<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> React.memo(Post);
</code></pre>
<p>In the code above, <code>Post</code> (functional component) depends on the <code>signedIn</code> and <code>post</code> props. By wrapping it with <code>React.memo()</code>, React will only re-render the <code>Post</code> component if either <code>signedIn</code> or <code>post</code> changes.</p>
<p>You can now use the memoized component like any other component in your application:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> Post <span class="hljs-keyword">from</span> <span class="hljs-string">'./Post'</span>;

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [signedIn, setSignedIn] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> post = { <span class="hljs-attr">title</span>: <span class="hljs-string">'Hello World'</span>, <span class="hljs-attr">content</span>: <span class="hljs-string">'Welcome to my blog!'</span> };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Post</span> <span class="hljs-attr">signedIn</span>=<span class="hljs-string">{signedIn}</span> <span class="hljs-attr">post</span>=<span class="hljs-string">{post}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setSignedIn(!signedIn)}&gt;
        Toggle Signed In
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>When you click the <code>Toggle Signed In</code> button, it will toggle the <code>signedIn</code> state. Since <code>Post</code> is wrapped with <code>React.memo()</code>, it will only re-render when the <code>signedIn</code> prop changes, thus saving rendering time and resources</p>
<h4 id="heading-how-to-use-usememo">How to use <code>useMemo()</code></h4>
<p>The <code>useMemo()</code> hook optimizes performance by memoizing the result of a function call or an expensive computation. It caches the result and recalculates it only when the input values change. Below is an example on how to use the <code>useMemo</code> hook in functional component:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useMemo } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [count, setCount] = React.useState(<span class="hljs-number">0</span>);
  <span class="hljs-keyword">const</span> [otherState, setOtherState] = React.useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> expensiveComputation = <span class="hljs-function">(<span class="hljs-params">num</span>) =&gt;</span> {
    <span class="hljs-keyword">let</span> i =  <span class="hljs-number">0</span>;
    <span class="hljs-keyword">while</span> (i &lt;  <span class="hljs-number">1000000000</span>) i++;
    <span class="hljs-keyword">return</span> num * num;
  };

  <span class="hljs-keyword">const</span> memoizedValue = useMemo(<span class="hljs-function">() =&gt;</span> expensiveComputation(count), [count]);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Count: {count}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Square: {memoizedValue}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setCount(count +  1)}&gt;Increase Count<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setOtherState(e.target.value)} /&gt;
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>In the code above, the <code>expensiveComputation</code> function simulates a resource-intensive operation, like squaring a number. </p>
<p>The <code>useMemo</code> hook is utilized to cache the result of this computation. The memoized value, stored in <code>memoizedValue</code>, is only recalculated when the <code>count</code> state changes, as <code>count</code> is specified as a dependency in the <code>useMemo</code> dependency array. Consequently, clicking the <code>Increase Count</code> button increments the <code>count</code> state, triggering a recalculation of the memoized value. </p>
<p>Conversely, changing the <code>otherState</code> via the input field does not prompt a recalculation, as <code>otherState</code> is not included in the <code>useMemo</code> dependency array.</p>
<h4 id="heading-how-to-use-usecallback">How to use <code>useCallback()</code></h4>
<p>The <code>useCallback()</code> hook in React is used to memoize a function instead of memoizing the function result. It is particularly useful when passing events as props to child components to prevent unnecessary re-renders. </p>
<p><code>useCallback()</code> memoizes the function, ensuring it remains the same across re-renders as long as the dependencies haven't changed. </p>
<p>This is especially beneficial when passing functions as props to child components, preventing unnecessary re-renders. It is often used with <code>React.memo()</code> to ensure child components do not re-render when unnecessary. Below is an exmple of how to use the <code>useCallback()</code> hook:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState, useCallback } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> ParentComponent = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [count, setCount] = useState(<span class="hljs-number">0</span>);

  <span class="hljs-comment">// Define a function that increments the count state</span>
  <span class="hljs-keyword">const</span> incrementCount = <span class="hljs-function">() =&gt;</span> {
    setCount(count + <span class="hljs-number">1</span>);
  };

  <span class="hljs-comment">// Memoize the incrementCount function using useCallback</span>
  <span class="hljs-keyword">const</span> memoizedIncrement = useCallback(incrementCount, [count]);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Count: {count}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ChildComponent</span> <span class="hljs-attr">onIncrement</span>=<span class="hljs-string">{memoizedIncrement}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">const</span> ChildComponent = React.memo(<span class="hljs-function">(<span class="hljs-params">{ onIncrement }</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Child component rendered'</span>);
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{onIncrement}</span>&gt;</span>Increment Count<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ParentComponent;
</code></pre>
<p>In the code above, the <code>ParentComponent</code> is responsible for managing a state variable named <code>count</code> and introduces a function called <code>incrementCount</code>, which handles the incrementation of the count. Utilizing the <code>useCallback</code> hook, the <code>incrementCount</code> function is memoized, guaranteeing its stability across renders unless any of its dependencies, in this case, <code>count</code>, undergo changes.</p>
<p>On the other hand, the <code>ChildComponent</code> is a component nested within the parent. It receives the memoized <code>onIncrement</code> function from the parent as a prop. </p>
<p>To optimize performance and prevent unnecessary re-renders when the props remain constant, the <code>ChildComponent</code> is wrapped with <code>React.memo()</code>. This ensures that the child component will only re-render when its props, specifically the memoized function, experience changes, contributing to a more efficient rendering process.</p>
<p>It's important to note that <code>useCallback</code> should be used sparingly and only for performance-critical parts of your application. Overusing <code>useCallback</code> can actually lead to worse performance due to the overhead of memoization itself. Always measure the performance impact before and after using <code>useCallback</code> to ensure it's having the desired effect.</p>
<h3 id="heading-throttling-and-debouncing-events">Throttling and Debouncing Events</h3>
<p>Throttling in React is a technique used to limit the number of times a function or an event handler is invoked. It ensures that the function is called at a specified interval, preventing it from being executed too frequently. </p>
<p>Throttling allows you to control the rate at which the function is called by setting up a minimum time interval between each function invocation. If the function is called multiple times within that interval, only the first invocation is executed, and subsequent invocations are ignored until the interval elapses</p>
<p>Now, let's illustrate throttling with a code example. First, without throttling:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Without throttling, this function will be called every time the event is triggered</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleResize</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Window resized'</span>);
}

<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'resize'</span>, handleResize);
</code></pre>
<p>With throttling, we can limit how often the <code>handleResize</code> function is called:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Throttling function</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">throttle</span>(<span class="hljs-params">func, delay</span>) </span>{
  <span class="hljs-keyword">let</span> lastCall =  <span class="hljs-number">0</span>;
  <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">...args</span>) </span>{
    <span class="hljs-keyword">const</span> now = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getTime();
    <span class="hljs-keyword">if</span> (now - lastCall &lt; delay) {
      <span class="hljs-keyword">return</span>;
    }
    lastCall = now;
    func(...args);
  };
}

<span class="hljs-comment">// Throttled event handler</span>
<span class="hljs-keyword">const</span> throttledHandleResize = throttle(handleResize,  <span class="hljs-number">200</span>);

<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'resize'</span>, throttledHandleResize)
</code></pre>
<p>In this example, the <code>throttle</code> function wraps <code>handleResize</code> and ensures it's not called more often than every 200 milliseconds. If the <code>resize</code> event fires more frequently than that, the <code>handleResize</code> function will only be executed once every 200 milliseconds, reducing the potential for performance issues caused by rapid, repeated function calls</p>
<p>Debouncing, on the other hand, is also used to limit the number of times a function or an event handler is invoked. It ensures that the function is called only after a certain period of inactivity. Debouncing allows you to postpone the function call until the user has finished typing or a specific time has elapsed since the last event.</p>
<p>For example, imagine you have a search input field and want to trigger a search API request only when the user has finished typing for a certain duration, like <code>300ms</code>. </p>
<p>With debouncing, the search function will only be invoked after the user stops typing for <code>300ms</code>. If the user continues typing within that interval, the function call will be delayed until the pause occurs. Without debouncing, the function will be called for every keystroke, potentially leading to excessive function calls and unnecessary computation. let's demonstrate with a code example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> SearchComponent = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [searchTerm, setSearchTerm] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-comment">// Function to simulate a search API request</span>
  <span class="hljs-keyword">const</span> searchAPI = <span class="hljs-function">(<span class="hljs-params">query</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Searching for: <span class="hljs-subst">${query}</span>`</span>);
    <span class="hljs-comment">// In a real application, you would make an API request here</span>
  };

  <span class="hljs-comment">// Debounce function to delay the searchAPI call</span>
  <span class="hljs-keyword">const</span> debounce = <span class="hljs-function">(<span class="hljs-params">func, delay</span>) =&gt;</span> {
    <span class="hljs-keyword">let</span> timeoutId;
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">...args</span>) </span>{
      <span class="hljs-built_in">clearTimeout</span>(timeoutId);
      timeoutId = <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
        func(...args);
      }, delay);
    };
  };

  <span class="hljs-comment">// Debounced search function</span>
  <span class="hljs-keyword">const</span> debouncedSearch = debounce(searchAPI, <span class="hljs-number">300</span>);

  <span class="hljs-comment">// useEffect to watch for changes in searchTerm and trigger debouncedSearch</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    debouncedSearch(searchTerm);
  }, [searchTerm, debouncedSearch]);

  <span class="hljs-comment">// Event handler for the search input</span>
  <span class="hljs-keyword">const</span> handleSearchChange = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
    setSearchTerm(event.target.value);
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"search"</span>&gt;</span>Search:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
        <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
        <span class="hljs-attr">id</span>=<span class="hljs-string">"search"</span>
        <span class="hljs-attr">value</span>=<span class="hljs-string">{searchTerm}</span>
        <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleSearchChange}</span>
        <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Type to search..."</span>
      /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> SearchComponent;
</code></pre>
<p>With this setup, the <code>searchAPI</code> function will only be invoked after the user stops typing for 300ms, preventing excessive API requests and improving the overall performance of the search functionality.</p>
<h3 id="heading-code-splitting">Code Splitting</h3>
<p>Code splitting in React is a technique used to split a large JavaScript bundle into smaller, manageable chunks. It helps improve performance by loading only the necessary code for a specific part of an application rather than loading the entire bundle upfront. </p>
<p>When you develop a new React application, all your JavaScript code is typically bundled together into a single file. This file contains all the components, libraries, and other code required for your application to function. But as your application grows, the bundle size can become quite large, resulting in slow initial load times for your users.</p>
<p>Code splitting allows you to divide a single bundle into multiple chunks, which can be loaded selectively based on the current needs of your application. Instead of downloading the entire bundle upfront, only the necessary code is fetched and executed when a user visits a particular page or triggers a specific action.</p>
<p>Below is a basic example of code splitting:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// AsyncComponent.js</span>
<span class="hljs-keyword">import</span> React, { lazy, Suspense } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> DynamicComponent = lazy(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./DynamicComponent'</span>));

<span class="hljs-keyword">const</span> AsyncComponent = <span class="hljs-function">() =&gt;</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Suspense</span> <span class="hljs-attr">fallback</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">div</span>&gt;</span>Loading...<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>}&gt;
    <span class="hljs-tag">&lt;<span class="hljs-name">DynamicComponent</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Suspense</span>&gt;</span></span>
);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AsyncComponent;


<span class="hljs-comment">// DynamicComponent.js</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> DynamicComponent = <span class="hljs-function">() =&gt;</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is a dynamically loaded component!<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> DynamicComponent;
</code></pre>
<p>In this example, <code>AsyncComponent</code> is a component that uses <code>lazy</code> and <code>Suspense</code> to perform code splitting. The <code>DynamicComponent</code> is dynamically imported using the import() syntax. </p>
<p>When <code>AsyncComponent</code> is rendered, React will load <code>DynamicComponent</code> only when it is needed, reducing the initial bundle size and improving the application's performance. The fallback prop in Suspense specifies what to render while waiting for the dynamic import to resolve, providing a better user experience during the loading process.</p>
<h3 id="heading-react-fragments">React Fragments</h3>
<p>React Fragments are a feature introduced in <a target="_blank" href="https://legacy.reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html">React 16.2</a> that allows you to group multiple elements together without adding an additional DOM node. This is particularly useful when you need to return multiple elements from a component's render method, but you don't want to introduce unnecessary DOM elements that could affect the layout or styles of your application.</p>
<p>Imagine you are arranging books on a bookshelf. Each book represents a React component, and the bookshelf represents the DOM. </p>
<p>Normally, if you have multiple books, you might want to group them together under a category label (analogous to a DOM element like a <code>&lt;div&gt;</code>). But sometimes you just want to place the books side by side without a label because the label itself doesn't hold any value and only takes up physical space. </p>
<p>React Fragments are like the option to arrange the books without a label, saving space and making the arrangement cleaner.</p>
<p>Here's an example of how to utilize React fragments:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">BookShelf</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Book</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"React for Beginners"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Book</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Mastering Redux"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Book</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"JavaScript Essentials"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Book</span>(<span class="hljs-params">{ title }</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>{title}<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span></span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> BookShelf;
</code></pre>
<p>In this example, the <code>BookShelf</code> component returns a list of <code>Book</code> components without wrapping them in a <code>&lt;div&gt;</code> or other unnecessary DOM element. Instead, it uses the <code>&lt;&gt;</code> shorthand syntax for React Fragments. </p>
<p>This results in a cleaner DOM structure, which can improve the performance of your React application by reducing the number of elements that the browser has to process and render. Using fragments can also reduce unnecessary markup and contribute to a cleaner and more efficient render tree.</p>
<h3 id="heading-web-workers">Web Workers</h3>
<p>JavaScript operates as a single-threaded application designed to handle synchronous tasks. </p>
<p>When a web page is being rendered, JavaScript executes multiple tasks, including manipulating DOM elements, managing UI interactions, handling API response data, and enabling CSS animations, all within a single thread. Despite its efficiency in managing these tasks, executing them in a single thread can sometimes lead to performance bottlenecks.</p>
<p>Web Workers serve as a solution to alleviate the burden on the main thread. They allow the execution of scripts in the background on a separate thread, distinct from the main JavaScript thread. </p>
<p>This separation enables the handling of computationally intensive tasks, execution of long-running operations, or management of tasks that might otherwise block the main thread. By doing so, Web Workers contribute to maintaining user interface responsiveness and overall application performance.</p>
<p>To use web worker in React, create a new JavaScript file that will contain the code for the worker thread:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// worker.js</span>
self.onmessage = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">event</span>) </span>{
  <span class="hljs-keyword">var</span> input = event.data;
  <span class="hljs-keyword">var</span> result = performHeavyComputation(input);
  postMessage(result);
};

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">performHeavyComputation</span>(<span class="hljs-params">input</span>) </span>{
  <span class="hljs-comment">// Insert your heavy computation logic here</span>
  <span class="hljs-keyword">return</span> input *   <span class="hljs-number">2</span>; <span class="hljs-comment">// Just a placeholder operation</span>
}
</code></pre>
<p>In your React component, instantiate the Web Worker and establish a communication channel with it:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useEffect, useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyComponent</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> workerRef = useRef();

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Initialize the worker</span>
    workerRef.current = <span class="hljs-keyword">new</span> Worker(<span class="hljs-string">'path-to-your-worker-file.js'</span>);

    <span class="hljs-comment">// Handle incoming messages from the worker</span>
    workerRef.current.onmessage = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Message received from worker:'</span>, event.data);
    };

    <span class="hljs-comment">// Cleanup the worker when the component unmounts</span>
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      workerRef.current.terminate();
    };
  }, []);

  <span class="hljs-comment">// Function to send a message to the worker</span>
  <span class="hljs-keyword">const</span> sendMessageToWorker = <span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> {
    workerRef.current.postMessage(message);
  };

  <span class="hljs-comment">// Rest of your component</span>
  <span class="hljs-keyword">return</span> (
    <span class="hljs-comment">// ...</span>
  );
}
</code></pre>
<p>In this example, a Web Worker is initialized in the <code>useEffect</code> hook and stored in a ref for future use. Messages from the worker are handled with an <code>onmessage</code> event listener, and the worker is terminated when the component is unmounted to clean up resources. The <code>sendMessageToWorker</code> function demonstrates how to communicate with the worker using <code>postMessage</code></p>
<h3 id="heading-usetransition-hook">UseTransition Hook</h3>
<p>The <code>useTransition</code> hook in React plays a pivotal role in improving the performance of applications by allowing the marking of state updates as non-blocking transitions. This capability enables React to defer rendering for these updates, preventing UI blocking and enhancing overall responsiveness. </p>
<p>When utilizing <code>useTransition,</code> state updates within the <code>startTransition</code> function are treated as low-priority transitions, susceptible to interruption by higher-priority state updates. So if a high-priority update occurs during a transition, React may prioritize finishing the high-priority update, interrupting the ongoing transition.</p>
<p>This non-blocking transition mechanism is valuable in preventing UI blocking during intensive operations such as data fetching or large-scale updates. By deferring the rendering of components associated with transition updates, React ensures that the user interface remains responsive even in scenarios where the UI might otherwise become unresponsive.</p>
<p>This example demonstrates the use of <code>useTransition</code> in a React component:</p>
<pre><code class="lang-javascript=">import React, { useState, useTransition } from 'react';

function MyComponent() {
  const [state, setState] = useState(initialState);
  const [isPending, startTransition] = useTransition();

  function handleClick() {
    startTransition(() =&gt; {
      setState(newState); // This state update is marked as a transition
    });
  }

  return (
    &lt;&gt;
      {/* Your component JSX */}
      &lt;button onClick={handleClick}&gt;Update State&lt;/button&gt;
      {isPending &amp;&amp; &lt;div&gt;Loading...&lt;/div&gt;}
    &lt;/&gt;
  );
}
</code></pre>
<p>This example showcases how React avoids blocking the UI during transitions triggered by user actions, allowing for interruption if higher-priority state updates are detected. </p>
<p>Note that <code>useTransition</code> is part of the Concurrent Mode API, introduced in React 18 and later versions. As a powerful tool for altering the default behavior of state updates, make sure you use it with care, considering the specific implications of deferring rendering within the context of your application.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Optimizing the performance of a React application involves a combination of strategies, from the fundamental understanding of React's diffing algorithm to leveraging built-in features and third-party tools. </p>
<p>By applying these techniques judiciously, you can create applications that are not only visually appealing but also performant, leading to a better overall user experience.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
