<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Subrata Kumar Das</title>
    <description>The latest articles on DEV Community by Subrata Kumar Das (@subraatakumar).</description>
    <link>https://dev.to/subraatakumar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1705863%2Fa3772c0b-286a-49ea-8edc-b23b70144507.png</url>
      <title>DEV Community: Subrata Kumar Das</title>
      <link>https://dev.to/subraatakumar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/subraatakumar"/>
    <language>en</language>
    <item>
      <title>Why I Chose Gemma 4 E2B for Subra AI: The Reality of Running LLMs on Mid-Range Phones</title>
      <dc:creator>Subrata Kumar Das</dc:creator>
      <pubDate>Tue, 01 Sep 2026 05:40:44 +0000</pubDate>
      <link>https://dev.to/subraatakumar/why-i-chose-gemma-4-e2b-for-subra-ai-the-reality-of-running-llms-on-mid-range-phones-4oal</link>
      <guid>https://dev.to/subraatakumar/why-i-chose-gemma-4-e2b-for-subra-ai-the-reality-of-running-llms-on-mid-range-phones-4oal</guid>
      <description>&lt;h2&gt;
  
  
  Introduction: The Anti-Cloud Philosophy
&lt;/h2&gt;

&lt;p&gt;In an era dominated by server-side APIs, &lt;a href="https://subraatakumar.com/subra-ai/" rel="noopener noreferrer"&gt;Subra AI&lt;/a&gt; was built to prove a point: you can have a highly intelligent, ChatGPT-like assistant that lives entirely on your device. No account creation, no external server handshakes, and absolute data privacy. &lt;/p&gt;

&lt;h2&gt;
  
  
  To pull off this architectural high-wire act across consumer hardware like iPhones, Android devices, and Desktop computers, I needed a model that was fiercely resource-efficient yet multi-modally competent. Enter Google DeepMind’s Gemma 4 E2B. 
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Reason 1: The "E" Factor — Punching Way Above Its Weight Budget
&lt;/h2&gt;

&lt;p&gt;The "E" in E2B stands for Effective Parameters. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;The Architecture: Gemma 4 E2B uses Per-Layer Embeddings (PLE). Instead of bloating the model with deeper layers, PLE gives each decoder layer its own tiny lookup embedding table for every token. &lt;/li&gt;
&lt;li&gt;The Benefit for Subra AI: It behaves with the semantic logic and reasoning depth of a 4B or 5B parameter model, but only executes 2.3 billion active parameters during generation. It’s the ultimate "cheat code" for getting dense intelligence inside a strict mobile sandboxed footprint. &lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Reason 2: Defeating the Mobile RAM Monster (1.5GB Threshold)
&lt;/h2&gt;

&lt;p&gt;When executing models locally via &lt;a href="https://developers.google.com/edge/litert-lm/models/gemma-4" rel="noopener noreferrer"&gt;Google LiteRT-LM&lt;/a&gt; (formerly MediaPipe), the biggest enemy isn't processing speed—it is operating system Out-Of-Memory (OOM) crashes. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;Mid-range Android phones and older iOS devices will ruthlessly kill background tasks or heavy applications consuming over 2 GB of volatile memory.
&lt;/li&gt;
&lt;li&gt;By deploying a 4-bit quantized version of Gemma 4 E2B, Subra AI runs comfortably with a baseline memory footprint of roughly 1.2 GB to 1.5 GB of RAM. This leaves plenty of headroom for the React Native engine and OS stability. &lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Reason 3: Native Multimodality for Real-World Vision Checkups
&lt;/h2&gt;

&lt;p&gt;A core feature of Subra AI is its ability to parse real-world data locally, such as scanning healthcare device readouts (blood pressure monitors or scales) and formatting them into structured JSON.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;Older open-weight models required external, bulky vision adapter encoders bolted onto the LLM.&lt;/li&gt;
&lt;li&gt;Gemma 4 E2B handles native image inputs natively. This tight integration keeps latency incredibly low, allowing Subra AI to instantly handle OCR, object detection, and document parsing on-device without crashing the phone's GPU/NPU cache. &lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Reason 4: Developer-Friendly Ecosystem Integration
&lt;/h2&gt;

&lt;p&gt;Building cross-platform apps requires tools that match the target environment. Google built Gemma 4 to natively integrate with Google AI Edge SDK / LiteRT. This means: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;No messy custom C++ inference wrapper debugging for Android.&lt;/li&gt;
&lt;li&gt;Seamless optimization on Apple Silicon execution lines.&lt;/li&gt;
&lt;li&gt;Native support for a 128K context window and strict system prompts. This allowed me to safely instruct Subra AI to act as a personal assistant without losing track of multi-turn chat history. &lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion: Why E2B Wins for the Edge
&lt;/h2&gt;

&lt;p&gt;Could I have used the larger Gemma 4 E4B or a 7B alternative? Yes, but at the cost of high battery drain, slower tokens-per-second, and alienation of users with mid-range phones. &lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Gemma 4 E2B was a calculated engineering choice: it gave Subra AI the exact blend of low-latency execution, native vision capture, and a manageable RAM footprint needed to build a premium, sovereign AI product. 
&lt;/h2&gt;

</description>
      <category>ai</category>
      <category>reactnative</category>
      <category>gemma</category>
      <category>ios</category>
    </item>
    <item>
      <title>Giving the AI Assisted Job Search a Local Brain</title>
      <dc:creator>Subrata Kumar Das</dc:creator>
      <pubDate>Fri, 28 Aug 2026 08:32:47 +0000</pubDate>
      <link>https://dev.to/subraatakumar/giving-the-ai-assisted-job-search-a-local-brain-278l</link>
      <guid>https://dev.to/subraatakumar/giving-the-ai-assisted-job-search-a-local-brain-278l</guid>
      <description>&lt;p&gt;&lt;em&gt;The next chapter in building an open-source, AI-assisted international job-search platform.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is the second article in the JobSearch series. In &lt;a href="https://dev.to/subraatakumar/building-an-open-source-ai-assisted-international-job-search-platform-39em"&gt;Part 1&lt;/a&gt;, I introduced the problem, the product direction, and the first foundations of the platform. This chapter is about what happened next: turning those foundations into a usable search journey and making local AI a first-class option.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/subraatakumar/job-search" rel="noopener noreferrer"&gt;github.com/subraatakumar/job-search&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The job search begins with a question
&lt;/h2&gt;

&lt;p&gt;Imagine opening a job-search application after a long day.&lt;/p&gt;

&lt;p&gt;You have a resume in one tab, a spreadsheet in another, saved searches scattered across several job boards, and a growing list of questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this role actually relevant to me?&lt;/li&gt;
&lt;li&gt;Does the company support international candidates?&lt;/li&gt;
&lt;li&gt;Is the listing still active?&lt;/li&gt;
&lt;li&gt;Which version of my experience should I highlight?&lt;/li&gt;
&lt;li&gt;Should I spend an hour applying to this role or move on?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first version of JobSearch was mostly about building the room: authentication, profiles, resume extraction, preferences, and a protected dashboard. This next version starts furnishing that room.&lt;/p&gt;

&lt;p&gt;The product is becoming a workspace where a candidate can describe what they are looking for, search across public sources, inspect the results, and eventually move from discovery to preparation without losing the thread.&lt;/p&gt;

&lt;p&gt;That last part matters. Job searching is not just retrieval. It is a sequence of decisions. The application should help people make better decisions, not make hundreds of noisy applications on their behalf.&lt;/p&gt;

&lt;h2&gt;
  
  
  From onboarding to a real workspace
&lt;/h2&gt;

&lt;p&gt;The onboarding path now has a clearer shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Profile → Job search preferences → AI provider → Search jobs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user can import a text-based PDF resume, review the extracted information, describe countries and roles of interest, choose an AI provider, and arrive at the dashboard search workspace.&lt;/p&gt;

&lt;p&gt;The final step is intentionally a dashboard rather than an unrelated jobs page. It is the place where the user’s profile, preferences, search conversation, filters, and future application workflow can meet.&lt;/p&gt;

&lt;p&gt;This is a small product decision with a large effect: the interface should follow the user’s mental model. After configuring the assistant, the next thing a person expects is to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why local AI belongs in this project
&lt;/h2&gt;

&lt;p&gt;Resumes and job preferences are personal. They contain employment history, skills, locations, contact details, and sometimes sensitive career decisions.&lt;/p&gt;

&lt;p&gt;Some users want the quality and convenience of a hosted model. Others want control over where their data goes. Some are experimenting, learning, or working with limited budgets. A single provider assumption would exclude too many of them.&lt;/p&gt;

&lt;p&gt;That is why JobSearch uses an OpenAI-compatible provider abstraction. The application can speak the same general API shape while allowing the model to live in different places:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hosted provider
       or
Local Ollama model
       or
Another compatible server
              ↓
      JobSearch AI adapter
              ↓
     Search, ranking, and preparation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The provider is explicit and configurable. API keys are stored server-side and encrypted with AES-256-GCM; they are never returned to the browser. Local Ollama users can leave the API key blank.&lt;/p&gt;

&lt;p&gt;This is not a claim that local models are always better. They can be slower, require more memory, and vary in quality. The point is choice. Privacy, cost, performance, and model quality are trade-offs that should belong to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Ollama locally
&lt;/h2&gt;

&lt;p&gt;Ollama makes it practical to run an AI model on your own machine. After installing it from &lt;a href="https://ollama.com/download" rel="noopener noreferrer"&gt;ollama.com/download&lt;/a&gt;, start the service and download a model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama serve
ollama pull llama3.2
ollama list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start JobSearch with Docker, open &lt;code&gt;/settings&lt;/code&gt;, and enter:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Provider&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ollama (local)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;llama3.2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API endpoint with Docker&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://host.docker.internal:11434/v1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API endpoint without Docker&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://localhost:11434/v1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API key&lt;/td&gt;
&lt;td&gt;Leave blank&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;host.docker.internal&lt;/code&gt; detail is important. When the web application runs inside Docker, &lt;code&gt;localhost&lt;/code&gt; means the container, not the host computer where Ollama is running. The host gateway lets the container reach the local Ollama service.&lt;/p&gt;

&lt;p&gt;After saving the provider, JobSearch takes the user to the dashboard search workspace. The model is now available for the parts of the workflow that need AI assistance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching is more than calling an endpoint
&lt;/h2&gt;

&lt;p&gt;The current search direction combines several responsibilities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Accept a natural-language request such as “senior React Native roles in Germany with visa support.”&lt;/li&gt;
&lt;li&gt;Discover candidate links from configured public sources and web search infrastructure.&lt;/li&gt;
&lt;li&gt;Verify individual pages where possible.&lt;/li&gt;
&lt;li&gt;Normalize titles, companies, locations, and URLs.&lt;/li&gt;
&lt;li&gt;Deduplicate the results.&lt;/li&gt;
&lt;li&gt;Optionally rank them against the user’s profile.&lt;/li&gt;
&lt;li&gt;Present evidence and useful explanations instead of a mysterious score.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That pipeline is deliberately server-side. The browser should be the place where users ask questions and review results, not the place where secrets, source orchestration, or provider credentials are exposed.&lt;/p&gt;

&lt;p&gt;The search interface also shows progress while discovery, source checking, verification, and ranking happen. This is more honest than displaying a spinner and pretending that every result has the same level of confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  A privacy boundary, not just a feature list
&lt;/h2&gt;

&lt;p&gt;Open-source AI projects often focus on what the model can generate. JobSearch also needs to be clear about what it will not do.&lt;/p&gt;

&lt;p&gt;The project is designed around public job information and user control. It does not collect passwords or cookies for gated platforms. It does not aim to bypass CAPTCHAs. It does not silently auto-apply to jobs. It should not fabricate qualifications, sponsorship evidence, or application claims.&lt;/p&gt;

&lt;p&gt;The intended workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find an opportunity
        ↓
Understand the evidence and fit
        ↓
Improve the application with assistance
        ↓
Review everything yourself
        ↓
Choose whether to apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That human review step is not a temporary limitation. It is part of the product philosophy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is working now
&lt;/h2&gt;

&lt;p&gt;The repository currently includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dockerized local development with PostgreSQL&lt;/li&gt;
&lt;li&gt;OAuth and PKCE authentication through the central Auth service&lt;/li&gt;
&lt;li&gt;Profile onboarding and review-first PDF resume extraction&lt;/li&gt;
&lt;li&gt;Multi-country search preferences with role, sponsorship, and frequency settings&lt;/li&gt;
&lt;li&gt;Hosted and local OpenAI-compatible AI provider configuration&lt;/li&gt;
&lt;li&gt;Encrypted server-side provider-key storage&lt;/li&gt;
&lt;li&gt;User and admin-managed public job sources&lt;/li&gt;
&lt;li&gt;Initial source refresh and job normalization&lt;/li&gt;
&lt;li&gt;Server-side web search orchestration with optional AI ranking&lt;/li&gt;
&lt;li&gt;A dashboard workspace for searching and reviewing opportunities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is still an early MVP. Some visible features are foundations rather than finished products. Search history, saved-job persistence, richer matching, and application preparation are active areas for improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;The next chapters will move from “find jobs” toward “understand and act.” The roadmap includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More reliable extraction from JavaScript-heavy career pages&lt;/li&gt;
&lt;li&gt;General web discovery through OpenSERP or another interchangeable provider&lt;/li&gt;
&lt;li&gt;Resume-to-job matching with transparent reasons&lt;/li&gt;
&lt;li&gt;Visa-sponsorship evidence and confidence indicators&lt;/li&gt;
&lt;li&gt;Tailored resume and cover-letter preparation&lt;/li&gt;
&lt;li&gt;Interview questions and practice based on a selected role&lt;/li&gt;
&lt;li&gt;Application status, reminders, and follow-up dates&lt;/li&gt;
&lt;li&gt;Scheduled searches and digest notifications&lt;/li&gt;
&lt;li&gt;More automated tests and evaluation datasets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The order may change. Real user feedback should influence it.&lt;/p&gt;

&lt;h2&gt;
  
  
  An invitation to build the next chapter
&lt;/h2&gt;

&lt;p&gt;JobSearch is intentionally open source because the job-search experience is different across countries, professions, industries, and career stages. One person may need sponsorship evidence. Another may need accessibility improvements. Someone else may know the best public career sources for a region that the project has not considered yet.&lt;/p&gt;

&lt;p&gt;You do not need to arrive with a large pull request. Contributions can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trying the local setup and reporting friction&lt;/li&gt;
&lt;li&gt;Suggesting a feature or a better onboarding flow&lt;/li&gt;
&lt;li&gt;Improving documentation&lt;/li&gt;
&lt;li&gt;Adding accessibility or responsive UI improvements&lt;/li&gt;
&lt;li&gt;Building a public-source connector&lt;/li&gt;
&lt;li&gt;Improving resume parsing or search evaluation&lt;/li&gt;
&lt;li&gt;Reviewing privacy and security decisions&lt;/li&gt;
&lt;li&gt;Testing Ollama with different models&lt;/li&gt;
&lt;li&gt;Helping define what “job fit” should mean in practice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Please start with the repository’s &lt;a href="https://github.com/subraatakumar/job-search/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt;, and open an issue for a substantial feature so the design can be discussed before implementation. Feature suggestions are especially welcome when they describe the user problem, not only the proposed button or endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stay for the next update
&lt;/h2&gt;

&lt;p&gt;The first article introduced the idea. This chapter gave the idea a local brain and a clearer destination: a search workspace that respects the person using it.&lt;/p&gt;

&lt;p&gt;The next update will focus on making results more trustworthy and useful—better source handling, stronger matching explanations, and the first steps toward turning a job listing into a tailored preparation plan.&lt;/p&gt;

&lt;p&gt;If you are searching internationally, building developer tools, interested in local AI, or simply curious about open-source product development, follow along. Try the project, tell me where the experience breaks, suggest what should be built next, and help shape the tool before the roadmap is set in stone.&lt;/p&gt;

&lt;p&gt;The best version of JobSearch will not come from one person guessing what everyone needs. It will come from people bringing their own job-search stories to the project.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;JobSearch is released under the MIT License. Please review the project’s security guidance before reporting sensitive issues or contributing integrations that handle personal data.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>gemma</category>
    </item>
    <item>
      <title>Building an Open-Source AI-Assisted International Job Search Platform</title>
      <dc:creator>Subrata Kumar Das</dc:creator>
      <pubDate>Tue, 25 Aug 2026 07:08:17 +0000</pubDate>
      <link>https://dev.to/subraatakumar/building-an-open-source-ai-assisted-international-job-search-platform-39em</link>
      <guid>https://dev.to/subraatakumar/building-an-open-source-ai-assisted-international-job-search-platform-39em</guid>
      <description>&lt;p&gt;Finding a job in another country is more complicated than searching for a job title.&lt;/p&gt;

&lt;p&gt;Candidates often need to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether the company supports visa sponsorship&lt;/li&gt;
&lt;li&gt;Whether the role matches their actual experience&lt;/li&gt;
&lt;li&gt;How to tailor their resume&lt;/li&gt;
&lt;li&gt;How to write a relevant cover letter&lt;/li&gt;
&lt;li&gt;How to prepare for the interview&lt;/li&gt;
&lt;li&gt;How to track applications and follow-ups&lt;/li&gt;
&lt;li&gt;Which opportunities are still active and trustworthy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m building an open-source project called &lt;strong&gt;JobSearch&lt;/strong&gt; to bring these activities into one workspace.&lt;/p&gt;

&lt;p&gt;GitHub repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/subraatakumar/job-search" rel="noopener noreferrer"&gt;https://github.com/subraatakumar/job-search&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project is still in its early MVP stage, but the initial Docker setup, authentication, resume import, and profile workflow are working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The product idea
&lt;/h2&gt;

&lt;p&gt;JobSearch is designed for people applying internationally.&lt;/p&gt;

&lt;p&gt;For example, a user in India might want to search for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Software engineering roles in Germany&lt;/li&gt;
&lt;li&gt;Companies that may support visa sponsorship&lt;/li&gt;
&lt;li&gt;Remote or relocation-friendly positions&lt;/li&gt;
&lt;li&gt;Jobs matching a specific technology stack&lt;/li&gt;
&lt;li&gt;Roles at a particular seniority level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The long-term workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Discover jobs
    ↓
Evaluate role and sponsorship fit
    ↓
Prepare a tailored application
    ↓
Practice for the interview
    ↓
Track the application
    ↓
Improve based on outcomes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not to automatically apply to hundreds of jobs. The goal is to help candidates make better, more informed applications while keeping the user in control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current MVP scope
&lt;/h2&gt;

&lt;p&gt;The first version focuses on building a trustworthy foundation.&lt;/p&gt;

&lt;p&gt;Currently implemented:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js and TypeScript application&lt;/li&gt;
&lt;li&gt;Dockerized local development&lt;/li&gt;
&lt;li&gt;PostgreSQL container&lt;/li&gt;
&lt;li&gt;Central authentication using OAuth and PKCE&lt;/li&gt;
&lt;li&gt;Protected dashboard&lt;/li&gt;
&lt;li&gt;Text-based PDF resume upload&lt;/li&gt;
&lt;li&gt;Server-side resume text extraction&lt;/li&gt;
&lt;li&gt;Review-first profile workflow&lt;/li&gt;
&lt;li&gt;Responsive landing page&lt;/li&gt;
&lt;li&gt;Open-source contributor setup script&lt;/li&gt;
&lt;li&gt;Separate local and production-like Docker modes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Planned features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structured profile fields&lt;/li&gt;
&lt;li&gt;Public job API integrations&lt;/li&gt;
&lt;li&gt;Company career page imports&lt;/li&gt;
&lt;li&gt;User-provided job URL imports&lt;/li&gt;
&lt;li&gt;Visa sponsorship evidence and confidence indicators&lt;/li&gt;
&lt;li&gt;Job matching&lt;/li&gt;
&lt;li&gt;Tailored resumes&lt;/li&gt;
&lt;li&gt;Job-specific cover letters&lt;/li&gt;
&lt;li&gt;Interview questions and practice answers&lt;/li&gt;
&lt;li&gt;Application status tracking&lt;/li&gt;
&lt;li&gt;Reminders and follow-up dates&lt;/li&gt;
&lt;li&gt;OpenAI-compatible AI providers&lt;/li&gt;
&lt;li&gt;Local Ollama support&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why open source?
&lt;/h2&gt;

&lt;p&gt;There are several reasons for making this project open source.&lt;/p&gt;

&lt;p&gt;First, job-search data is personal and sensitive. Users should be able to understand where their data goes and how the application works.&lt;/p&gt;

&lt;p&gt;Second, different users will want different AI providers. Some may prefer a hosted provider, while others may want to run a local model through Ollama.&lt;/p&gt;

&lt;p&gt;Third, job sources vary by country. A community can help add lawful, documented integrations for different regions without tightly coupling the core application to a single provider.&lt;/p&gt;

&lt;p&gt;The project is released under the MIT License.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical architecture
&lt;/h2&gt;

&lt;p&gt;The current architecture is intentionally simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   ↓
Next.js application
   ↓
PostgreSQL
   ↓
AI provider adapter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application will eventually include a background worker for scheduled searches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scheduler
   ↓
Job source connectors
   ↓
Normalization and deduplication
   ↓
Job database
   ↓
User notifications and matching
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The planned technology stack is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;Docker Compose&lt;/li&gt;
&lt;li&gt;OAuth with PKCE&lt;/li&gt;
&lt;li&gt;OpenAI-compatible AI provider interface&lt;/li&gt;
&lt;li&gt;Optional local Ollama endpoint&lt;/li&gt;
&lt;li&gt;Background scheduler and worker&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reusing a central authentication service
&lt;/h2&gt;

&lt;p&gt;The project uses an existing central authentication service based on Better Auth.&lt;/p&gt;

&lt;p&gt;The authentication service provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google sign-in&lt;/li&gt;
&lt;li&gt;OAuth authorization-code flow&lt;/li&gt;
&lt;li&gt;PKCE&lt;/li&gt;
&lt;li&gt;Central user identity&lt;/li&gt;
&lt;li&gt;Product-specific OAuth clients&lt;/li&gt;
&lt;li&gt;Separate product-local sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The JobSearch application does not directly share authentication cookies or the authentication database.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JobSearch
   ↓
Redirect to central Auth service
   ↓
User signs in
   ↓
Auth redirects back with an authorization code
   ↓
JobSearch exchanges the code using PKCE
   ↓
JobSearch creates its own local session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the authentication boundary clear and allows other applications to reuse the same identity service without sharing application data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the project locally
&lt;/h2&gt;

&lt;p&gt;The project is designed to be easy to run with Docker.&lt;/p&gt;

&lt;p&gt;Requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker Desktop&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Node.js 22 or newer for non-Docker development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clone the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/subraatakumar/job-search.git
&lt;span class="nb"&gt;cd &lt;/span&gt;job-search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the local environment file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set a strong session secret in &lt;code&gt;.env&lt;/code&gt;, then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./re-run-local.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local services are available at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JobSearch:  http://localhost:3020
Dashboard:  http://localhost:3020/dashboard
Profile:    http://localhost:3020/profile
PostgreSQL: localhost:5433
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To stop the services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The normal shutdown command does not delete the PostgreSQL volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production-like local mode
&lt;/h2&gt;

&lt;p&gt;The project also includes a separate production-like mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.prod.example .env.prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure the production values and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./re-run-local.sh prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mode uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JobSearch:  http://localhost:3021
PostgreSQL: localhost:5434
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Cloudflare Tunnel can route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs.example.com → http://localhost:3021
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local and production-like modes use separate Compose projects and separate database volumes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resume import
&lt;/h2&gt;

&lt;p&gt;The first version supports text-based PDF resumes.&lt;/p&gt;

&lt;p&gt;The workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upload PDF
    ↓
Extract text
    ↓
Create draft profile
    ↓
User reviews the information
    ↓
Confirm the master profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application does not yet treat extracted text as confirmed truth.&lt;/p&gt;

&lt;p&gt;This is important because resume parsing can produce errors. A parser might misunderstand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dates&lt;/li&gt;
&lt;li&gt;Job titles&lt;/li&gt;
&lt;li&gt;Company names&lt;/li&gt;
&lt;li&gt;Technologies&lt;/li&gt;
&lt;li&gt;Education details&lt;/li&gt;
&lt;li&gt;Links&lt;/li&gt;
&lt;li&gt;Formatting and section boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The user must review the extracted information before it can be used to generate an application.&lt;/p&gt;

&lt;p&gt;Scanned or image-only PDF files are not supported in the MVP. OCR is planned for a future version after evaluating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accuracy&lt;/li&gt;
&lt;li&gt;Privacy&lt;/li&gt;
&lt;li&gt;Processing cost&lt;/li&gt;
&lt;li&gt;Multilingual resume support&lt;/li&gt;
&lt;li&gt;Layout complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AI provider flexibility
&lt;/h2&gt;

&lt;p&gt;The project is designed to support OpenAI-compatible endpoints.&lt;/p&gt;

&lt;p&gt;The long-term goal is to let users configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hosted AI providers&lt;/li&gt;
&lt;li&gt;OpenAI-compatible APIs&lt;/li&gt;
&lt;li&gt;Local Ollama endpoints&lt;/li&gt;
&lt;li&gt;Other compatible model servers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is important because users have different requirements around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Privacy&lt;/li&gt;
&lt;li&gt;Cost&lt;/li&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Model quality&lt;/li&gt;
&lt;li&gt;Data residency&lt;/li&gt;
&lt;li&gt;Offline usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application should never silently send a user's resume or profile data to an AI provider. Provider configuration and consent must be explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy and safety decisions
&lt;/h2&gt;

&lt;p&gt;The project handles sensitive information, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resumes&lt;/li&gt;
&lt;li&gt;Contact details&lt;/li&gt;
&lt;li&gt;Employment history&lt;/li&gt;
&lt;li&gt;Education&lt;/li&gt;
&lt;li&gt;Application notes&lt;/li&gt;
&lt;li&gt;Interview preparation&lt;/li&gt;
&lt;li&gt;Job-search preferences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important design principles include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never commit secrets or personal data&lt;/li&gt;
&lt;li&gt;Do not store API keys in browser code&lt;/li&gt;
&lt;li&gt;Do not log access tokens or session cookies&lt;/li&gt;
&lt;li&gt;Do not fabricate user qualifications&lt;/li&gt;
&lt;li&gt;Keep generated documents linked to their source job&lt;/li&gt;
&lt;li&gt;Require user review before application submission&lt;/li&gt;
&lt;li&gt;Provide data export and deletion capabilities&lt;/li&gt;
&lt;li&gt;Preserve source evidence for job information where possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project will not implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cookie extraction&lt;/li&gt;
&lt;li&gt;CAPTCHA bypassing&lt;/li&gt;
&lt;li&gt;Credential theft&lt;/li&gt;
&lt;li&gt;Unauthorized scraping&lt;/li&gt;
&lt;li&gt;Automated applications against platforms that prohibit automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Job sources should use official APIs, permitted feeds, company career pages, user-provided URLs, or other lawful methods.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current UI
&lt;/h2&gt;

&lt;p&gt;The landing page explains the product workflow and includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;International job-search positioning&lt;/li&gt;
&lt;li&gt;Resume and profile setup&lt;/li&gt;
&lt;li&gt;AI-assisted application preparation&lt;/li&gt;
&lt;li&gt;Interview preparation&lt;/li&gt;
&lt;li&gt;Application tracking&lt;/li&gt;
&lt;li&gt;Local and hosted AI provider support&lt;/li&gt;
&lt;li&gt;Open-source project access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The profile page includes a drag-and-drop PDF upload area and extracted resume preview.&lt;/p&gt;

&lt;p&gt;The UI is being polished screen by screen instead of waiting for a large redesign at the end. This allows the product to remain usable while the underlying features are developed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’m looking for
&lt;/h2&gt;

&lt;p&gt;I would especially welcome feedback on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which job sources should be supported first?&lt;/li&gt;
&lt;li&gt;How should visa sponsorship evidence be displayed?&lt;/li&gt;
&lt;li&gt;What profile information is essential for international job matching?&lt;/li&gt;
&lt;li&gt;What should the first job-search dashboard include?&lt;/li&gt;
&lt;li&gt;Which AI providers should be supported first?&lt;/li&gt;
&lt;li&gt;Should the app remain local-first, or become a hosted multi-user platform?&lt;/li&gt;
&lt;li&gt;What would make this useful for your own job search?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;Contributions are welcome.&lt;/p&gt;

&lt;p&gt;Before contributing, please read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CONTRIBUTING.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SECURITY.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CODE_OF_CONDUCT.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project is still early, so useful contributions may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI improvements&lt;/li&gt;
&lt;li&gt;Accessibility fixes&lt;/li&gt;
&lt;li&gt;Resume parsing improvements&lt;/li&gt;
&lt;li&gt;Job-source connectors&lt;/li&gt;
&lt;li&gt;Database design&lt;/li&gt;
&lt;li&gt;AI provider adapters&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Privacy and security reviews&lt;/li&gt;
&lt;li&gt;Product feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Please keep pull requests focused and include testing or verification steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;International job searching is often fragmented across job boards, spreadsheets, documents, notes, and interview preparation tools.&lt;/p&gt;

&lt;p&gt;JobSearch is an attempt to bring those activities together while keeping the user in control of their data and AI provider.&lt;/p&gt;

&lt;p&gt;The project is early, but the foundation is now in place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It runs locally with Docker&lt;/li&gt;
&lt;li&gt;Authentication works&lt;/li&gt;
&lt;li&gt;The dashboard is protected&lt;/li&gt;
&lt;li&gt;Resume text extraction works&lt;/li&gt;
&lt;li&gt;The project is publicly documented&lt;/li&gt;
&lt;li&gt;The repository is open source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this problem interests you, I would love feedback, ideas, issues, and contributions.&lt;/p&gt;

&lt;p&gt;GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/subraatakumar/job-search" rel="noopener noreferrer"&gt;https://github.com/subraatakumar/job-search&lt;/a&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>opensource</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>ai</category>
    </item>
    <item>
      <title>Subra AI: Private Offline On-Device AI</title>
      <dc:creator>Subrata Kumar Das</dc:creator>
      <pubDate>Fri, 24 Jul 2026 12:39:23 +0000</pubDate>
      <link>https://dev.to/subraatakumar/subra-ai-private-offline-on-device-ai-187m</link>
      <guid>https://dev.to/subraatakumar/subra-ai-private-offline-on-device-ai-187m</guid>
      <description></description>
    </item>
    <item>
      <title>Synchronous TurboModules Are Not Automatically Fast</title>
      <dc:creator>Subrata Kumar Das</dc:creator>
      <pubDate>Wed, 15 Jul 2026 05:14:04 +0000</pubDate>
      <link>https://dev.to/subraatakumar/synchronous-turbomodules-are-not-automatically-fast-1fb6</link>
      <guid>https://dev.to/subraatakumar/synchronous-turbomodules-are-not-automatically-fast-1fb6</guid>
      <description>&lt;p&gt;React Native’s New Architecture gives JavaScript more direct access to native functionality.&lt;/p&gt;

&lt;p&gt;One important capability is synchronous communication through TurboModules.&lt;/p&gt;

&lt;p&gt;That can be useful.&lt;/p&gt;

&lt;p&gt;It can also introduce a performance problem that is easy to miss:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A native method being synchronous does not mean the native work is fast.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The communication mechanism may be efficient, but the JavaScript thread must still wait for the native operation to finish.&lt;/p&gt;

&lt;p&gt;That difference matters in production applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  The misleading simplicity of a synchronous API
&lt;/h2&gt;

&lt;p&gt;Consider a native method exposed like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SecureStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the JavaScript side, it looks harmless.&lt;/p&gt;

&lt;p&gt;There is no &lt;code&gt;await&lt;/code&gt;, no callback and no visible asynchronous workflow.&lt;/p&gt;

&lt;p&gt;The value is returned immediately from the developer’s point of view.&lt;/p&gt;

&lt;p&gt;But the native implementation may perform several operations before returning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;access Android Keystore or iOS Keychain&lt;/li&gt;
&lt;li&gt;decrypt stored data&lt;/li&gt;
&lt;li&gt;read from disk&lt;/li&gt;
&lt;li&gt;parse JSON&lt;/li&gt;
&lt;li&gt;convert native objects into JavaScript-compatible values&lt;/li&gt;
&lt;li&gt;communicate with another system service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this work takes time, JavaScript cannot continue until the method returns.&lt;/p&gt;

&lt;p&gt;The API looks simple.&lt;/p&gt;

&lt;p&gt;The execution may not be.&lt;/p&gt;




&lt;h2&gt;
  
  
  What synchronous actually means
&lt;/h2&gt;

&lt;p&gt;A synchronous call means the caller waits for the result.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NativeModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;renderScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;renderScreen&lt;/code&gt; cannot begin until &lt;code&gt;getValue()&lt;/code&gt; finishes.&lt;/p&gt;

&lt;p&gt;That behaviour is acceptable when the native operation is extremely small and predictable.&lt;/p&gt;

&lt;p&gt;It becomes dangerous when the operation has variable latency.&lt;/p&gt;

&lt;p&gt;The same method may take:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;less than one millisecond when data is cached&lt;/li&gt;
&lt;li&gt;several milliseconds when data requires conversion&lt;/li&gt;
&lt;li&gt;much longer when storage, cryptography or system services are involved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The JavaScript thread does not care why the native method is delayed.&lt;/p&gt;

&lt;p&gt;It simply remains blocked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this affects React Native performance
&lt;/h2&gt;

&lt;p&gt;React Native applications perform important work on the JavaScript thread.&lt;/p&gt;

&lt;p&gt;This can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;processing state updates&lt;/li&gt;
&lt;li&gt;running application logic&lt;/li&gt;
&lt;li&gt;handling user interactions&lt;/li&gt;
&lt;li&gt;preparing React renders&lt;/li&gt;
&lt;li&gt;coordinating navigation&lt;/li&gt;
&lt;li&gt;executing animation-related JavaScript&lt;/li&gt;
&lt;li&gt;responding to events from native modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a synchronous native method blocks that thread, other JavaScript work must wait.&lt;/p&gt;

&lt;p&gt;A single call may not create an obvious problem.&lt;/p&gt;

&lt;p&gt;Repeated calls can.&lt;/p&gt;

&lt;p&gt;Imagine that a component reads a secure value during rendering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AccountScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SecureStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AccountDetails&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine that the component renders multiple times during navigation or state updates.&lt;/p&gt;

&lt;p&gt;Every render may trigger another native operation.&lt;/p&gt;

&lt;p&gt;If the method accesses storage and performs decryption, the application is repeatedly blocking the JavaScript thread inside the render path.&lt;/p&gt;

&lt;p&gt;The visible result may be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slow navigation&lt;/li&gt;
&lt;li&gt;delayed button responses&lt;/li&gt;
&lt;li&gt;dropped animation frames&lt;/li&gt;
&lt;li&gt;inconsistent screen transitions&lt;/li&gt;
&lt;li&gt;temporary UI freezes&lt;/li&gt;
&lt;li&gt;performance that becomes worse on lower-end devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The developer may initially investigate React re-renders.&lt;/p&gt;

&lt;p&gt;The real problem is the synchronous native work performed during those renders.&lt;/p&gt;




&lt;h2&gt;
  
  
  TurboModules reduce overhead, not execution cost
&lt;/h2&gt;

&lt;p&gt;This is the central distinction.&lt;/p&gt;

&lt;p&gt;TurboModules improve how JavaScript and native code communicate.&lt;/p&gt;

&lt;p&gt;They can reduce some of the overhead associated with the older bridge architecture.&lt;/p&gt;

&lt;p&gt;But TurboModules do not make the native operation itself free.&lt;/p&gt;

&lt;p&gt;Suppose a native method performs three tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read secure storage
        ↓
Decrypt the value
        ↓
Convert it into a JavaScript object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The communication layer may be efficient.&lt;/p&gt;

&lt;p&gt;The application must still pay for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;storage access&lt;/li&gt;
&lt;li&gt;cryptographic processing&lt;/li&gt;
&lt;li&gt;object allocation&lt;/li&gt;
&lt;li&gt;data conversion&lt;/li&gt;
&lt;li&gt;thread coordination&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optimising the path between JavaScript and native code does not eliminate the work happening at the destination.&lt;/p&gt;

&lt;p&gt;A faster doorway does not make the room behind it smaller.&lt;/p&gt;




&lt;h2&gt;
  
  
  When synchronous native methods are appropriate
&lt;/h2&gt;

&lt;p&gt;Synchronous methods are useful when the operation is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tiny&lt;/li&gt;
&lt;li&gt;deterministic&lt;/li&gt;
&lt;li&gt;memory-resident&lt;/li&gt;
&lt;li&gt;frequently required&lt;/li&gt;
&lt;li&gt;guaranteed not to perform slow I/O&lt;/li&gt;
&lt;li&gt;predictable across supported devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples may include reading a cached capability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supportsBiometrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DeviceCapabilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasBiometricHardware&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or checking a native constant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;AppConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEnvironment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or performing a very small calculation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pixels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DisplayUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dpToPixels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These operations are generally suitable only when their native implementation remains lightweight.&lt;/p&gt;

&lt;p&gt;The important question is not whether the return type is small.&lt;/p&gt;

&lt;p&gt;The important question is how the value is produced.&lt;/p&gt;

&lt;p&gt;Returning one boolean can still be expensive if the implementation queries a system service every time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Operations that should usually remain asynchronous
&lt;/h2&gt;

&lt;p&gt;Native methods should normally be asynchronous when they involve:&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SecureStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Disk and secure-storage access may have unpredictable latency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cryptography
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decryptedPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;CryptoModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cryptographic work can become expensive depending on the algorithm and input size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Networking
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NativeNetworkClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchConfiguration&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Network operations are naturally unpredictable and should never block JavaScript synchronously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Large data conversion
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NativeDatabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Converting thousands of native records into JavaScript objects can consume considerable time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image and file processing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ImageProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Media processing is computationally expensive and should normally run away from latency-sensitive application work.&lt;/p&gt;

&lt;h3&gt;
  
  
  System services
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;LocationModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentPosition&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response time may depend on permissions, sensors, device state and operating-system behaviour.&lt;/p&gt;




&lt;h2&gt;
  
  
  Avoid synchronous work inside rendering
&lt;/h2&gt;

&lt;p&gt;One of the strongest review rules is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not perform synchronous native work inside a React render path unless the operation is proven to be trivial.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is risky:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NativeSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSettings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DashboardContent&lt;/span&gt; &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A better design is to load the value asynchronously and store it in application state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSettings&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Settings&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadSettings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NativeSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSettings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;loadSettings&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LoadingScreen&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DashboardContent&lt;/span&gt; &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact state-management approach may differ.&lt;/p&gt;

&lt;p&gt;The principle remains the same:&lt;/p&gt;

&lt;p&gt;Do not make rendering wait for native storage, cryptography or system resources.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cache deliberately, not accidentally
&lt;/h2&gt;

&lt;p&gt;Sometimes a synchronous API is created because the application needs immediate access to a value.&lt;/p&gt;

&lt;p&gt;The safer solution may be to load the value earlier and maintain an in-memory cache.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionStore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;initialise&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SecureStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;getCachedToken&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The native storage operation remains asynchronous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sessionStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later reads are synchronous because they access application memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sessionStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCachedToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a meaningful distinction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;loading from storage is asynchronous&lt;/li&gt;
&lt;li&gt;reading an already-loaded value is synchronous&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is much safer than hiding storage access behind a synchronous API.&lt;/p&gt;

&lt;p&gt;However, caching introduces its own responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cache invalidation&lt;/li&gt;
&lt;li&gt;logout handling&lt;/li&gt;
&lt;li&gt;token refresh&lt;/li&gt;
&lt;li&gt;memory lifecycle&lt;/li&gt;
&lt;li&gt;multi-process consistency&lt;/li&gt;
&lt;li&gt;protection of sensitive values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to make everything synchronous.&lt;/p&gt;

&lt;p&gt;The goal is to place expensive work outside latency-sensitive paths.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measure the whole native method
&lt;/h2&gt;

&lt;p&gt;A common performance mistake is to measure only JavaScript execution.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SecureStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Completed in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can reveal blocking time from the JavaScript side.&lt;/p&gt;

&lt;p&gt;But deeper investigation may require native instrumentation.&lt;/p&gt;

&lt;p&gt;On Android, developers can measure sections using native timing tools or platform profilers.&lt;/p&gt;

&lt;p&gt;On iOS, Instruments can help identify time spent in storage, cryptography, object conversion and thread blocking.&lt;/p&gt;

&lt;p&gt;The useful questions are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How long does the method take on average?&lt;/li&gt;
&lt;li&gt;What is the worst observed latency?&lt;/li&gt;
&lt;li&gt;Does the duration change when the value is not cached?&lt;/li&gt;
&lt;li&gt;Does the method perform disk access?&lt;/li&gt;
&lt;li&gt;Does it allocate or convert large objects?&lt;/li&gt;
&lt;li&gt;Is it called during rendering or navigation?&lt;/li&gt;
&lt;li&gt;How many times is it called during one user action?&lt;/li&gt;
&lt;li&gt;How does it behave on lower-end devices?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Average timing alone is not enough.&lt;/p&gt;

&lt;p&gt;A method that normally completes quickly but occasionally blocks for 40 milliseconds can still create visible UI problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Watch for repeated synchronous calls
&lt;/h2&gt;

&lt;p&gt;The cost of one method call may appear acceptable.&lt;/p&gt;

&lt;p&gt;The call frequency may be the actual problem.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;NativeFormatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there are 500 items, the application performs 500 synchronous native calls.&lt;/p&gt;

&lt;p&gt;Even if each call takes only a fraction of a millisecond, the combined cost can become significant.&lt;/p&gt;

&lt;p&gt;A stronger native API might process the entire collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formattedItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NativeFormatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formatBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can reduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repeated boundary crossings&lt;/li&gt;
&lt;li&gt;repeated allocations&lt;/li&gt;
&lt;li&gt;thread coordination&lt;/li&gt;
&lt;li&gt;per-call conversion overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Batching is especially useful when working with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;database rows&lt;/li&gt;
&lt;li&gt;image metadata&lt;/li&gt;
&lt;li&gt;sensor records&lt;/li&gt;
&lt;li&gt;BLE data&lt;/li&gt;
&lt;li&gt;file lists&lt;/li&gt;
&lt;li&gt;cryptographic operations&lt;/li&gt;
&lt;li&gt;large arrays produced by native SDKs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The correct API shape often matters as much as the implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Synchronous methods create architectural coupling
&lt;/h2&gt;

&lt;p&gt;There is another risk beyond immediate performance.&lt;/p&gt;

&lt;p&gt;A synchronous API can become slow later.&lt;/p&gt;

&lt;p&gt;Version one may return an in-memory value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getUserRegion&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A future requirement may change the implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read the region from encrypted storage&lt;/li&gt;
&lt;li&gt;migrate an old value&lt;/li&gt;
&lt;li&gt;consult remote configuration&lt;/li&gt;
&lt;li&gt;query a native SDK&lt;/li&gt;
&lt;li&gt;support multiple user profiles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The JavaScript interface remains synchronous, but the native responsibility has become heavier.&lt;/p&gt;

&lt;p&gt;Now the team faces a difficult choice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep blocking JavaScript&lt;/li&gt;
&lt;li&gt;introduce hidden caching&lt;/li&gt;
&lt;li&gt;change the public API&lt;/li&gt;
&lt;li&gt;maintain both synchronous and asynchronous versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For values likely to require I/O in the future, starting with an asynchronous contract is often safer.&lt;/p&gt;

&lt;p&gt;API design should consider how the operation may evolve, not only how it works today.&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical decision rule
&lt;/h2&gt;

&lt;p&gt;Before exposing a synchronous TurboModule method, ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can this method always complete quickly without storage, networking, cryptography, large conversion or unpredictable system work?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is uncertain, use an asynchronous API.&lt;/p&gt;

&lt;p&gt;A useful classification is:&lt;/p&gt;

&lt;h3&gt;
  
  
  Suitable for synchronous execution
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;constants&lt;/li&gt;
&lt;li&gt;cached primitives&lt;/li&gt;
&lt;li&gt;tiny calculations&lt;/li&gt;
&lt;li&gt;in-memory flags&lt;/li&gt;
&lt;li&gt;deterministic capability checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Better suited for asynchronous execution
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;file access&lt;/li&gt;
&lt;li&gt;secure storage&lt;/li&gt;
&lt;li&gt;databases&lt;/li&gt;
&lt;li&gt;networking&lt;/li&gt;
&lt;li&gt;encryption and decryption&lt;/li&gt;
&lt;li&gt;compression&lt;/li&gt;
&lt;li&gt;image processing&lt;/li&gt;
&lt;li&gt;large data conversion&lt;/li&gt;
&lt;li&gt;hardware interaction&lt;/li&gt;
&lt;li&gt;operating-system services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The method’s name is not enough.&lt;/p&gt;

&lt;p&gt;Review the complete native execution path.&lt;/p&gt;




&lt;h2&gt;
  
  
  Code-review checklist
&lt;/h2&gt;

&lt;p&gt;When reviewing a synchronous TurboModule method, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does it perform disk or secure-storage access?&lt;/li&gt;
&lt;li&gt;Does it call another SDK or system service?&lt;/li&gt;
&lt;li&gt;Does it perform cryptography?&lt;/li&gt;
&lt;li&gt;Can it return a large object or array?&lt;/li&gt;
&lt;li&gt;Does it parse or serialise data?&lt;/li&gt;
&lt;li&gt;Can its latency change based on device state?&lt;/li&gt;
&lt;li&gt;Is it called during rendering?&lt;/li&gt;
&lt;li&gt;Is it called repeatedly inside a loop?&lt;/li&gt;
&lt;li&gt;Is the result already available in memory?&lt;/li&gt;
&lt;li&gt;Could the method become more expensive later?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One concerning answer may be enough to reconsider the synchronous design.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;TurboModules can improve React Native’s native integration model.&lt;/p&gt;

&lt;p&gt;They make direct and synchronous interactions possible where appropriate.&lt;/p&gt;

&lt;p&gt;But capability should not become default behaviour.&lt;/p&gt;

&lt;p&gt;The safest rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use synchronous native methods only for tiny, predictable and memory-resident operations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Keep storage, cryptography, networking, large data processing and unpredictable system work asynchronous.&lt;/p&gt;

&lt;p&gt;TurboModules reduce communication overhead.&lt;/p&gt;

&lt;p&gt;They do not remove the cost of native execution.&lt;/p&gt;

&lt;p&gt;Where have you seen a synchronous native method create unexpected UI latency in a React Native application?&lt;/p&gt;




</description>
      <category>reactnative</category>
      <category>mobile</category>
      <category>ai</category>
    </item>
    <item>
      <title>Type Safety Stops at Runtime Boundaries in React Native</title>
      <dc:creator>Subrata Kumar Das</dc:creator>
      <pubDate>Mon, 13 Jul 2026 14:31:13 +0000</pubDate>
      <link>https://dev.to/subraatakumar/type-safety-stops-at-runtime-boundaries-in-react-native-ian</link>
      <guid>https://dev.to/subraatakumar/type-safety-stops-at-runtime-boundaries-in-react-native-ian</guid>
      <description>&lt;p&gt;TypeScript can make a React Native codebase safer.&lt;/p&gt;

&lt;p&gt;It can catch missing properties, incorrect function arguments, invalid component props and many other mistakes before the application runs.&lt;/p&gt;

&lt;p&gt;But TypeScript has one important limitation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It cannot verify whether external data actually matches the type you assigned to it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This becomes especially important in React Native because application data often crosses multiple runtime boundaries.&lt;/p&gt;

&lt;p&gt;A value may come from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a native Android module&lt;/li&gt;
&lt;li&gt;an iOS native module&lt;/li&gt;
&lt;li&gt;an API response&lt;/li&gt;
&lt;li&gt;persisted storage&lt;/li&gt;
&lt;li&gt;a deep link&lt;/li&gt;
&lt;li&gt;remote configuration&lt;/li&gt;
&lt;li&gt;an analytics SDK&lt;/li&gt;
&lt;li&gt;an AI-generated JSON response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At each of these boundaries, the actual runtime value may differ from the TypeScript definition.&lt;/p&gt;

&lt;p&gt;And TypeScript may not warn you.&lt;/p&gt;




&lt;h2&gt;
  
  
  A simple example
&lt;/h2&gt;

&lt;p&gt;Assume a React Native application receives battery information from a native module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isCharging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JavaScript layer expects a value like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;batteryStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;isCharging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks safe.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;level&lt;/code&gt; field is a number.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;isCharging&lt;/code&gt; field is a boolean.&lt;/p&gt;

&lt;p&gt;Now imagine that the Android implementation changes and returns this instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;82&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isCharging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;level&lt;/code&gt; value is now a string.&lt;/p&gt;

&lt;p&gt;At runtime, JavaScript receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;batteryStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But TypeScript may still believe that &lt;code&gt;level&lt;/code&gt; is a number.&lt;/p&gt;

&lt;p&gt;How can that happen?&lt;/p&gt;

&lt;p&gt;Because the result may have been cast.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;batteryStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;NativeModules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BatteryModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cast tells TypeScript:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Trust me. This value is a &lt;code&gt;BatteryStatus&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TypeScript accepts that instruction.&lt;/p&gt;

&lt;p&gt;It does not inspect the Android or iOS runtime value.&lt;/p&gt;




&lt;h2&gt;
  
  
  Casting is not validation
&lt;/h2&gt;

&lt;p&gt;This distinction is important.&lt;/p&gt;

&lt;p&gt;A type cast changes what TypeScript believes.&lt;/p&gt;

&lt;p&gt;It does not change or verify the actual value.&lt;/p&gt;

&lt;p&gt;Consider this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;82&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript may allow the code.&lt;/p&gt;

&lt;p&gt;But the runtime value is still a string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why the following assumption is dangerous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;externalValue&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ExpectedType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code may compile successfully while the application remains unsafe.&lt;/p&gt;

&lt;p&gt;A better mental model is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Casting changes the compiler's understanding. Validation checks reality.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Where the failure appears
&lt;/h2&gt;

&lt;p&gt;Boundary problems do not always fail immediately.&lt;/p&gt;

&lt;p&gt;That is one reason they can be difficult to diagnose.&lt;/p&gt;

&lt;p&gt;The application may receive the incorrect value on one screen, store it in state and fail much later.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextLevel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;batteryStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;level&lt;/code&gt; is a number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="mi"&gt;82&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// 92&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;level&lt;/code&gt; is a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;82&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// "8210"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app may not crash.&lt;/p&gt;

&lt;p&gt;It may simply produce the wrong result.&lt;/p&gt;

&lt;p&gt;That is often worse because the problem can remain hidden.&lt;/p&gt;

&lt;p&gt;The value may also fail during:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;numeric comparisons&lt;/li&gt;
&lt;li&gt;sorting&lt;/li&gt;
&lt;li&gt;formatting&lt;/li&gt;
&lt;li&gt;progress calculations&lt;/li&gt;
&lt;li&gt;conditional rendering&lt;/li&gt;
&lt;li&gt;database queries&lt;/li&gt;
&lt;li&gt;analytics processing&lt;/li&gt;
&lt;li&gt;business-rule evaluation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batteryStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;showLowBatteryWarning&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript may coerce the value and produce a result.&lt;/p&gt;

&lt;p&gt;But relying on implicit coercion makes application behaviour fragile.&lt;/p&gt;




&lt;h2&gt;
  
  
  Compile-time trust should stop at runtime boundaries
&lt;/h2&gt;

&lt;p&gt;Inside a properly typed TypeScript system, values can usually be trusted.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;showBatteryStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If every caller passes a real &lt;code&gt;BatteryStatus&lt;/code&gt;, the function is safe.&lt;/p&gt;

&lt;p&gt;The problem begins when a value enters the system from outside.&lt;/p&gt;

&lt;p&gt;That value has not been created or verified by TypeScript.&lt;/p&gt;

&lt;p&gt;Therefore, the safer rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Treat external values as &lt;code&gt;unknown&lt;/code&gt; until they have been validated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;NativeModules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BatteryModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NativeModules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BatteryModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the application is forced to prove that the value is safe before using it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why &lt;code&gt;unknown&lt;/code&gt; is better than &lt;code&gt;any&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Many boundary values are typed as &lt;code&gt;any&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchBatteryStatus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes most type protection.&lt;/p&gt;

&lt;p&gt;You can access anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invalidProperty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;anythingAtAll&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript will not stop you.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;unknown&lt;/code&gt; behaves differently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchBatteryStatus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You cannot safely access its properties until you narrow the type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// TypeScript error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This may initially feel inconvenient.&lt;/p&gt;

&lt;p&gt;But that inconvenience is useful.&lt;/p&gt;

&lt;p&gt;It forces the code to validate the runtime value.&lt;/p&gt;




&lt;h2&gt;
  
  
  Manual runtime validation
&lt;/h2&gt;

&lt;p&gt;A simple type guard can validate the native response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isCharging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isBatteryStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCharging&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the boundary can be handled safely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NativeModules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BatteryModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isBatteryStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawStatus&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid battery status returned by native module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the validation succeeds, TypeScript knows that &lt;code&gt;rawStatus&lt;/code&gt; is a valid &lt;code&gt;BatteryStatus&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is a safe conversion from an untrusted value into a trusted application type.&lt;/p&gt;




&lt;h2&gt;
  
  
  Validate business rules, not only field types
&lt;/h2&gt;

&lt;p&gt;Checking whether &lt;code&gt;level&lt;/code&gt; is a number may not be enough.&lt;/p&gt;

&lt;p&gt;The value may still be invalid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isCharging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shape is correct.&lt;/p&gt;

&lt;p&gt;But a battery percentage of &lt;code&gt;400&lt;/code&gt; is not valid.&lt;/p&gt;

&lt;p&gt;A stronger validator should also verify allowed ranges.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isBatteryStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCharging&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runtime validation may need to check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;required fields&lt;/li&gt;
&lt;li&gt;primitive types&lt;/li&gt;
&lt;li&gt;allowed ranges&lt;/li&gt;
&lt;li&gt;enum values&lt;/li&gt;
&lt;li&gt;string formats&lt;/li&gt;
&lt;li&gt;nested object shapes&lt;/li&gt;
&lt;li&gt;array item types&lt;/li&gt;
&lt;li&gt;mutually exclusive states&lt;/li&gt;
&lt;li&gt;domain-specific rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A structurally correct object can still be invalid for the application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using a schema validation library
&lt;/h2&gt;

&lt;p&gt;Manual validation works well for small objects.&lt;/p&gt;

&lt;p&gt;For larger payloads, a schema library can make the code easier to maintain.&lt;/p&gt;

&lt;p&gt;A common approach is to define a schema and derive the TypeScript type from it.&lt;/p&gt;

&lt;p&gt;For example, using Zod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatusSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;isCharging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatusSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The external value can then be parsed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NativeModules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BatteryModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatusSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawStatus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid battery status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is not the specific library.&lt;/p&gt;

&lt;p&gt;The important part is the boundary discipline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive untrusted data.&lt;/li&gt;
&lt;li&gt;Validate it.&lt;/li&gt;
&lt;li&gt;Convert it into a trusted type.&lt;/li&gt;
&lt;li&gt;Use it inside the application.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Runtime boundaries in React Native
&lt;/h2&gt;

&lt;p&gt;React Native applications contain more boundaries than many teams realise.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Native modules
&lt;/h3&gt;

&lt;p&gt;Native modules may return different types across Android and iOS.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Android returns an integer&lt;/li&gt;
&lt;li&gt;iOS returns a floating-point number&lt;/li&gt;
&lt;li&gt;one platform returns &lt;code&gt;null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the other platform omits the field&lt;/li&gt;
&lt;li&gt;a native SDK returns a string representation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generated TurboModule specifications can improve contract safety, but runtime validation may still be useful when data originates from external native SDKs, persisted native state or platform APIs.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Network responses
&lt;/h3&gt;

&lt;p&gt;An API client may declare a generic response type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not guarantee that the server returned a valid &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It only tells TypeScript how the code intends to use the response.&lt;/p&gt;

&lt;p&gt;The backend may return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while the application expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;API types are expectations until runtime validation confirms them.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Persisted storage
&lt;/h3&gt;

&lt;p&gt;Data stored in AsyncStorage, MMKV, SQLite or another persistence layer can become stale.&lt;/p&gt;

&lt;p&gt;The application model may change between releases.&lt;/p&gt;

&lt;p&gt;An older app version may have saved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"theme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dark"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A newer version may expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;fontScale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading stored JSON and casting it directly can create hidden problems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storedValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parsing confirms only that the value is valid JSON.&lt;/p&gt;

&lt;p&gt;It does not confirm that it matches &lt;code&gt;Settings&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Deep links
&lt;/h3&gt;

&lt;p&gt;Deep-link parameters are external input.&lt;/p&gt;

&lt;p&gt;A link may contain missing, malformed or unexpected values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;myapp://payment?amount=abc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application should not assume that &lt;code&gt;amount&lt;/code&gt; is a valid number merely because the navigation type expects one.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Remote configuration
&lt;/h3&gt;

&lt;p&gt;Remote configuration can change without an application release.&lt;/p&gt;

&lt;p&gt;A field that was previously boolean may accidentally be configured as a string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"newCheckoutEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without validation, the application may treat a non-empty string as truthy and enable behaviour unexpectedly.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. AI-generated structured output
&lt;/h3&gt;

&lt;p&gt;AI-generated JSON should always be treated as untrusted.&lt;/p&gt;

&lt;p&gt;Even when a model is instructed to follow a specific schema, it may:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;omit required fields&lt;/li&gt;
&lt;li&gt;return additional fields&lt;/li&gt;
&lt;li&gt;use incorrect enum values&lt;/li&gt;
&lt;li&gt;produce malformed JSON&lt;/li&gt;
&lt;li&gt;return numbers as strings&lt;/li&gt;
&lt;li&gt;generate semantically invalid combinations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"extremely_urgent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retryCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-3"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response may look structured while still violating the application contract.&lt;/p&gt;

&lt;p&gt;AI output requires both structural validation and business-rule validation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Validate once at the edge
&lt;/h2&gt;

&lt;p&gt;Runtime validation should not be scattered throughout the entire application.&lt;/p&gt;

&lt;p&gt;That creates repetitive and inconsistent checks.&lt;/p&gt;

&lt;p&gt;A stronger architecture validates values at the edge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getBatteryStatus&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BatteryStatus&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;rawStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NativeModules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BatteryModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatusSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawStatus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of the application can now depend on the trusted function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getBatteryStatus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;renderBatteryLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the codebase a clear separation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Untrusted external data
        ↓
Boundary validation
        ↓
Trusted application model
        ↓
Business logic and UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The validation cost is paid once.&lt;/p&gt;

&lt;p&gt;The trusted type can then flow safely through the internal system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Parse, do not merely check
&lt;/h2&gt;

&lt;p&gt;In some cases, the application may intentionally transform external data.&lt;/p&gt;

&lt;p&gt;Suppose the native module returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;82&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isCharging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application may decide that numeric strings are acceptable.&lt;/p&gt;

&lt;p&gt;Instead of rejecting the value, the boundary parser can normalise it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseBatteryStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;BatteryStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Battery status must be an object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid battery level&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCharging&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid charging state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isCharging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCharging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is often called parsing rather than validation.&lt;/p&gt;

&lt;p&gt;Validation asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this value already valid?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Parsing asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can this value be safely converted into the trusted form?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both approaches are useful.&lt;/p&gt;

&lt;p&gt;The choice should be explicit.&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical code-review checklist
&lt;/h2&gt;

&lt;p&gt;Whenever a value enters a React Native application, ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Where did this value come from?&lt;/li&gt;
&lt;li&gt;Is the source controlled by the TypeScript compiler?&lt;/li&gt;
&lt;li&gt;Is the value typed as &lt;code&gt;any&lt;/code&gt; or cast using &lt;code&gt;as&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;What happens if a field is missing?&lt;/li&gt;
&lt;li&gt;What happens if a number arrives as a string?&lt;/li&gt;
&lt;li&gt;Are null and undefined handled differently?&lt;/li&gt;
&lt;li&gt;Are allowed ranges and enum values checked?&lt;/li&gt;
&lt;li&gt;Is validation performed once at the boundary?&lt;/li&gt;
&lt;li&gt;Is the failure logged with enough context?&lt;/li&gt;
&lt;li&gt;Can the application recover safely from invalid data?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These questions are especially important during:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;native-module reviews&lt;/li&gt;
&lt;li&gt;API integration&lt;/li&gt;
&lt;li&gt;storage migrations&lt;/li&gt;
&lt;li&gt;SDK upgrades&lt;/li&gt;
&lt;li&gt;backend contract changes&lt;/li&gt;
&lt;li&gt;AI feature development&lt;/li&gt;
&lt;li&gt;platform-specific debugging&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;TypeScript is excellent at preserving correctness inside a typed system.&lt;/p&gt;

&lt;p&gt;But it does not control the values coming from outside that system.&lt;/p&gt;

&lt;p&gt;A type definition describes what the application expects.&lt;/p&gt;

&lt;p&gt;A type cast tells the compiler what to believe.&lt;/p&gt;

&lt;p&gt;Runtime validation checks what the application actually received.&lt;/p&gt;

&lt;p&gt;The engineering rule is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Treat external values as &lt;code&gt;unknown&lt;/code&gt;, validate them at the boundary and only then convert them into trusted application types.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because in production React Native applications:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Casting is not validation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Which runtime boundary has caused the most unexpected type failure in your React Native application?&lt;/p&gt;




</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>Your Offline-First App Can Lose Correct Data Without Showing Any Error</title>
      <dc:creator>Subrata Kumar Das</dc:creator>
      <pubDate>Sun, 12 Jul 2026 07:07:30 +0000</pubDate>
      <link>https://dev.to/subraatakumar/your-offline-first-app-can-lose-correct-data-without-showing-any-error-1bm4</link>
      <guid>https://dev.to/subraatakumar/your-offline-first-app-can-lose-correct-data-without-showing-any-error-1bm4</guid>
      <description>&lt;p&gt;An offline-first application can complete synchronization successfully and still produce the wrong result.&lt;/p&gt;

&lt;p&gt;The API may return &lt;code&gt;200 OK&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The local mutation queue may become empty.&lt;/p&gt;

&lt;p&gt;The device may show that all changes are synchronized.&lt;/p&gt;

&lt;p&gt;And yet, one valid update may have silently overwritten another.&lt;/p&gt;

&lt;p&gt;This is one of the most dangerous offline-first failures because nothing appears broken technically.&lt;/p&gt;

&lt;p&gt;There is no crash.&lt;/p&gt;

&lt;p&gt;There is no network error.&lt;/p&gt;

&lt;p&gt;There is no failed request.&lt;/p&gt;

&lt;p&gt;The system behaves exactly as implemented.&lt;/p&gt;

&lt;p&gt;The problem is that the implementation never defined which version of the data should win.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Failure Scenario
&lt;/h2&gt;

&lt;p&gt;Consider a maintenance application used by field technicians and supervisors.&lt;/p&gt;

&lt;p&gt;A technician opens a maintenance request while working in an area with poor connectivity.&lt;/p&gt;

&lt;p&gt;The current request contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REQ-1042&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MEDIUM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Initial inspection pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The technician goes offline and updates the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IN_PROGRESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Inspection started at the site&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before the technician reconnects, a supervisor opens the same request through a web dashboard and changes the priority:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HIGH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Potential safety risk reported&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now two valid versions exist.&lt;/p&gt;

&lt;p&gt;The technician’s device contains one valid update.&lt;/p&gt;

&lt;p&gt;The server contains another valid update.&lt;/p&gt;

&lt;p&gt;When the device reconnects, the mobile application sends its pending mutation.&lt;/p&gt;

&lt;p&gt;The difficult question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do we upload the local data?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The difficult question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which version should be trusted?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why Last-Write-Wins Is Not Always Conflict Resolution
&lt;/h2&gt;

&lt;p&gt;A common implementation compares timestamps and keeps the latest update.&lt;/p&gt;

&lt;p&gt;This is known as last-write-wins.&lt;/p&gt;

&lt;p&gt;For some fields, this may be acceptable.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a profile description&lt;/li&gt;
&lt;li&gt;a temporary draft&lt;/li&gt;
&lt;li&gt;a non-critical display preference&lt;/li&gt;
&lt;li&gt;an editable note where losing an earlier version is acceptable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But last-write-wins becomes dangerous when the conflicting data includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;approval status&lt;/li&gt;
&lt;li&gt;inspection findings&lt;/li&gt;
&lt;li&gt;financial values&lt;/li&gt;
&lt;li&gt;compliance records&lt;/li&gt;
&lt;li&gt;safety-related decisions&lt;/li&gt;
&lt;li&gt;healthcare information&lt;/li&gt;
&lt;li&gt;task ownership&lt;/li&gt;
&lt;li&gt;inventory quantities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Imagine that the technician’s update is submitted five seconds after the supervisor’s update.&lt;/p&gt;

&lt;p&gt;A timestamp-based strategy may replace the supervisor’s high-priority safety warning with the technician’s older local copy.&lt;/p&gt;

&lt;p&gt;The synchronization request succeeds.&lt;/p&gt;

&lt;p&gt;The server accepts the payload.&lt;/p&gt;

&lt;p&gt;The local queue is cleared.&lt;/p&gt;

&lt;p&gt;But an important update has been lost.&lt;/p&gt;

&lt;p&gt;That is not a networking failure.&lt;/p&gt;

&lt;p&gt;It is an architecture failure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Offline-First Is a Business-Rules Problem
&lt;/h2&gt;

&lt;p&gt;Offline-first architecture is often described using technical components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQLite or Realm&lt;/li&gt;
&lt;li&gt;local persistence&lt;/li&gt;
&lt;li&gt;mutation queues&lt;/li&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;background synchronization&lt;/li&gt;
&lt;li&gt;connectivity listeners&lt;/li&gt;
&lt;li&gt;optimistic UI updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These components are important.&lt;/p&gt;

&lt;p&gt;But they only solve the mechanics of storing and transporting data.&lt;/p&gt;

&lt;p&gt;They do not decide what should happen when two correct versions disagree.&lt;/p&gt;

&lt;p&gt;That decision belongs to business rules.&lt;/p&gt;

&lt;p&gt;Before building the synchronization engine, every important field or operation should have a conflict-resolution policy.&lt;/p&gt;

&lt;p&gt;A useful starting point is to define four possible outcomes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Merge&lt;/li&gt;
&lt;li&gt;Reject&lt;/li&gt;
&lt;li&gt;Overwrite&lt;/li&gt;
&lt;li&gt;Review&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Merge
&lt;/h2&gt;

&lt;p&gt;Use merging when both updates can safely coexist.&lt;/p&gt;

&lt;p&gt;Suppose the server contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;electrical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The offline device adds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;urgent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of replacing one array with the other, the system can merge them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;electrical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;urgent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Append-only activity logs are another good candidate for merging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ActivityEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;createdBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each action becomes a separate immutable event.&lt;/p&gt;

&lt;p&gt;The system adds new events instead of replacing the complete history.&lt;/p&gt;

&lt;p&gt;This makes synchronization safer because concurrent actions do not compete for ownership of the same record.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Reject
&lt;/h2&gt;

&lt;p&gt;Some fields should be controlled only by the server.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;approvedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manager-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;approvedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-07-12T10:30:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;complianceStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VERIFIED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A mobile device should not be allowed to overwrite these values from an old local snapshot.&lt;/p&gt;

&lt;p&gt;If an offline mutation contains server-owned fields, the backend should reject those parts of the update.&lt;/p&gt;

&lt;p&gt;A field ownership model may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fieldOwnership&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;approvedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;approvedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;complianceStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shared&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shared&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;localDraft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client can still display server-owned fields while offline.&lt;/p&gt;

&lt;p&gt;But it should not treat them as locally authoritative.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Overwrite
&lt;/h2&gt;

&lt;p&gt;Overwriting is acceptable when the business explicitly allows one side to win.&lt;/p&gt;

&lt;p&gt;For example, a user may edit a local draft on multiple devices, and the product may intentionally use the latest saved version.&lt;/p&gt;

&lt;p&gt;The important point is that overwrite should be a documented rule, not an accidental side effect.&lt;/p&gt;

&lt;p&gt;Instead of saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The latest request happened to win.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The architecture should say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This field uses last-write-wins because losing the earlier value is acceptable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That difference matters.&lt;/p&gt;

&lt;p&gt;An accidental overwrite is a bug.&lt;/p&gt;

&lt;p&gt;An intentional overwrite is a policy.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Review
&lt;/h2&gt;

&lt;p&gt;Some conflicts cannot be resolved safely by software.&lt;/p&gt;

&lt;p&gt;Consider these two changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Technician update&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;inspectionResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SAFE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Supervisor update&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;inspectionResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REQUIRES_REVIEW&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatically choosing either version may create operational risk.&lt;/p&gt;

&lt;p&gt;The correct outcome may be to preserve both versions and require a person to resolve the conflict.&lt;/p&gt;

&lt;p&gt;A conflict record could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SyncConflict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;entityId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;localValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;serverValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;localUpdatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;serverUpdatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;resolutionStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RESOLVED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can show a dedicated conflict-resolution screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inspection result changed in two places.

Local version:
SAFE

Server version:
REQUIRES_REVIEW

Choose a value or escalate for supervisor review.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is less convenient than automatic synchronization.&lt;/p&gt;

&lt;p&gt;But convenience should not override correctness for critical data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Version Numbers, Not Only Timestamps
&lt;/h2&gt;

&lt;p&gt;Timestamps are useful, but they are not always reliable enough for concurrency control.&lt;/p&gt;

&lt;p&gt;Devices may have incorrect clocks.&lt;/p&gt;

&lt;p&gt;Requests may be delayed.&lt;/p&gt;

&lt;p&gt;Two updates may happen within the same timestamp precision.&lt;/p&gt;

&lt;p&gt;A stronger approach is optimistic concurrency control using version numbers.&lt;/p&gt;

&lt;p&gt;The server stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REQ-1042&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client sends the version it originally edited:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;PATCH /maintenance-requests/REQ-1042
If-Match: 4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payload may contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IN_PROGRESS"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the server still has version &lt;code&gt;4&lt;/code&gt;, the mutation can be accepted.&lt;/p&gt;

&lt;p&gt;The server updates the record to version &lt;code&gt;5&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the server already has version &lt;code&gt;5&lt;/code&gt;, the client edited an outdated copy.&lt;/p&gt;

&lt;p&gt;The server can return a conflict response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;409 Conflict
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VERSION_CONFLICT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currentVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"serverRecord"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REQ-1042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OPEN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HIGH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client now has enough information to merge, reject, overwrite or request human review.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Simple React Native Mutation Queue
&lt;/h2&gt;

&lt;p&gt;An offline mutation should contain more than an endpoint and payload.&lt;/p&gt;

&lt;p&gt;It should also preserve the version that the user originally edited.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PendingMutation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;entityType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;maintenanceRequest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;entityId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;baseVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;retryCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example mutation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PendingMutation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mutation-501&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;entityType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;maintenanceRequest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;entityId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REQ-1042&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;baseVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IN_PROGRESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Inspection started at the site&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;retryCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During synchronization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncMutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PendingMutation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/maintenance-requests/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entityId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PATCH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;If-Match&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseVersion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;conflict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFLICT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;serverRecord&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;conflict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serverRecord&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Sync failed with status &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that a &lt;code&gt;409 Conflict&lt;/code&gt; should not be treated like a normal retryable network error.&lt;/p&gt;

&lt;p&gt;Retrying the same request repeatedly will not resolve a semantic conflict.&lt;/p&gt;

&lt;p&gt;The system needs a conflict-resolution rule.&lt;/p&gt;




&lt;h2&gt;
  
  
  Classify Sync Failures Correctly
&lt;/h2&gt;

&lt;p&gt;Not every failed synchronization attempt means the same thing.&lt;/p&gt;

&lt;p&gt;A useful classification is:&lt;/p&gt;

&lt;h3&gt;
  
  
  Network failure
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no connectivity&lt;/li&gt;
&lt;li&gt;request timeout&lt;/li&gt;
&lt;li&gt;DNS failure&lt;/li&gt;
&lt;li&gt;temporary server unavailability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retry with backoff&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Authentication failure
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;expired token&lt;/li&gt;
&lt;li&gt;revoked session&lt;/li&gt;
&lt;li&gt;missing permission&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;refresh credentials or require authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Validation failure
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;invalid field value&lt;/li&gt;
&lt;li&gt;required field missing&lt;/li&gt;
&lt;li&gt;unsupported state transition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;show the user what must be corrected&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Version conflict
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;server record changed after the local copy was created&lt;/li&gt;
&lt;li&gt;two users edited the same field&lt;/li&gt;
&lt;li&gt;an approval occurred while the device was offline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;merge, reject, overwrite or review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treating all four categories as “sync failed” creates weak architecture and confusing user experiences.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do Not Delete the Local Mutation Too Early
&lt;/h2&gt;

&lt;p&gt;A dangerous synchronization pattern looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendMutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;removeMutationFromQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is safe only when the server has confirmed that the mutation was accepted and correctly applied.&lt;/p&gt;

&lt;p&gt;The mutation should remain available when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the server returns a conflict&lt;/li&gt;
&lt;li&gt;only part of the payload was accepted&lt;/li&gt;
&lt;li&gt;conflict review is pending&lt;/li&gt;
&lt;li&gt;the response cannot be validated&lt;/li&gt;
&lt;li&gt;the application crashes before local state is updated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A safer state machine might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MutationStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SYNCING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFLICT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;COMPLETED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The queue entry should move to &lt;code&gt;COMPLETED&lt;/code&gt; only after the client has processed the final server response.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conflict Resolution Should Be Field-Aware
&lt;/h2&gt;

&lt;p&gt;Record-level conflict resolution is often too broad.&lt;/p&gt;

&lt;p&gt;Suppose the local version changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Inspection completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the same time, the server changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HIGH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These updates affect different fields.&lt;/p&gt;

&lt;p&gt;Rejecting the entire local mutation would be unnecessary.&lt;/p&gt;

&lt;p&gt;A field-aware merge can preserve both changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Inspection completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HIGH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But consider this case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Local&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;COMPLETED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CANCELLED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both updates affect the same field.&lt;/p&gt;

&lt;p&gt;The conflict policy for &lt;code&gt;status&lt;/code&gt; must now decide whether to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reject the local transition&lt;/li&gt;
&lt;li&gt;keep the server value&lt;/li&gt;
&lt;li&gt;ask for review&lt;/li&gt;
&lt;li&gt;apply a domain-specific state machine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why conflict resolution must understand business meaning, not only JSON structure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Define the Rules Before Selecting the Database
&lt;/h2&gt;

&lt;p&gt;Teams often begin offline-first discussions with questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should we use Realm?&lt;/li&gt;
&lt;li&gt;Should we use SQLite?&lt;/li&gt;
&lt;li&gt;Should we use WatermelonDB?&lt;/li&gt;
&lt;li&gt;How should we structure the retry queue?&lt;/li&gt;
&lt;li&gt;Which connectivity library should we use?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are useful implementation questions.&lt;/p&gt;

&lt;p&gt;But they come after more important architecture questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which fields are client-owned?&lt;/li&gt;
&lt;li&gt;Which fields are server-owned?&lt;/li&gt;
&lt;li&gt;Which operations are append-only?&lt;/li&gt;
&lt;li&gt;Which fields can use last-write-wins?&lt;/li&gt;
&lt;li&gt;Which conflicts require human review?&lt;/li&gt;
&lt;li&gt;How long should unresolved conflicts be retained?&lt;/li&gt;
&lt;li&gt;Can users continue editing while a conflict is pending?&lt;/li&gt;
&lt;li&gt;What audit trail is required?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A database can persist conflicting data.&lt;/p&gt;

&lt;p&gt;It cannot decide which version is correct.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Practical Conflict-Policy Table
&lt;/h2&gt;

&lt;p&gt;Before implementation, document the policy for important fields.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field or Operation&lt;/th&gt;
&lt;th&gt;Ownership&lt;/th&gt;
&lt;th&gt;Resolution Strategy&lt;/th&gt;
&lt;th&gt;Human Review&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User notes&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;Last-write-wins or merge history&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approval status&lt;/td&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Reject local overwrite&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Activity log&lt;/td&gt;
&lt;td&gt;Append-only&lt;/td&gt;
&lt;td&gt;Merge&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inspection result&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;Compare versions&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compliance status&lt;/td&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Server wins&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Draft description&lt;/td&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;Local overwrite allowed&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assigned technician&lt;/td&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Reject stale update&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table becomes a shared contract between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mobile developers&lt;/li&gt;
&lt;li&gt;backend developers&lt;/li&gt;
&lt;li&gt;product managers&lt;/li&gt;
&lt;li&gt;QA engineers&lt;/li&gt;
&lt;li&gt;security teams&lt;/li&gt;
&lt;li&gt;compliance stakeholders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this contract, each layer may implement a different interpretation of “sync.”&lt;/p&gt;




&lt;h2&gt;
  
  
  Questions to Ask During Architecture Review
&lt;/h2&gt;

&lt;p&gt;Before calling an application offline-first, ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What happens when local and server records both change?&lt;/li&gt;
&lt;li&gt;Does every mutation include a base version?&lt;/li&gt;
&lt;li&gt;Which fields are controlled by the server?&lt;/li&gt;
&lt;li&gt;Which data can be merged safely?&lt;/li&gt;
&lt;li&gt;Which fields may use last-write-wins?&lt;/li&gt;
&lt;li&gt;Which conflicts must be reviewed by a person?&lt;/li&gt;
&lt;li&gt;Is conflict history preserved for auditing?&lt;/li&gt;
&lt;li&gt;Can the user see when synchronization is blocked?&lt;/li&gt;
&lt;li&gt;Are semantic conflicts separated from network failures?&lt;/li&gt;
&lt;li&gt;When is a mutation permanently removed from the queue?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the team cannot answer these questions, the application may support offline storage, but it does not yet have a complete offline-first architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;Offline-first architecture is not only about local databases, queues and retry mechanisms.&lt;/p&gt;

&lt;p&gt;Those components answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How will data move when connectivity returns?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Conflict policies answer the more important question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should happen when two valid versions disagree?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before building the synchronization engine, define four possible outcomes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merge. Reject. Overwrite. Review.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make those decisions per field or operation.&lt;/p&gt;

&lt;p&gt;Add version numbers to mutations.&lt;/p&gt;

&lt;p&gt;Keep unresolved conflicts visible.&lt;/p&gt;

&lt;p&gt;And never assume that a successful API response means the correct data survived.&lt;/p&gt;

&lt;p&gt;Which field in your application would be dangerous to resolve using last-write-wins?&lt;/p&gt;




</description>
      <category>reactnative</category>
      <category>mobilearchitecture</category>
      <category>architecture</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Your React Native Performance Problem May Actually Be a Memory Leak</title>
      <dc:creator>Subrata Kumar Das</dc:creator>
      <pubDate>Sat, 11 Jul 2026 15:15:15 +0000</pubDate>
      <link>https://dev.to/subraatakumar/your-react-native-performance-problem-may-actually-be-a-memory-leak-1jpi</link>
      <guid>https://dev.to/subraatakumar/your-react-native-performance-problem-may-actually-be-a-memory-leak-1jpi</guid>
      <description>&lt;h1&gt;
  
  
  Your React Native Performance Problem May Actually Be a Memory Leak
&lt;/h1&gt;

&lt;p&gt;A React Native screen can render correctly and still leak memory.&lt;/p&gt;

&lt;p&gt;That is why some performance problems are misdiagnosed.&lt;/p&gt;

&lt;p&gt;The app feels fine after launch.&lt;/p&gt;

&lt;p&gt;Then, after repeated navigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;screens become slower&lt;/li&gt;
&lt;li&gt;callbacks fire more than once&lt;/li&gt;
&lt;li&gt;background work keeps increasing&lt;/li&gt;
&lt;li&gt;network listeners remain active&lt;/li&gt;
&lt;li&gt;timers continue running&lt;/li&gt;
&lt;li&gt;state updates happen after a screen has already unmounted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first reaction is often:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Let us optimise re-renders.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But re-rendering may not be the real problem.&lt;/p&gt;

&lt;p&gt;The actual issue may be that work created by old screens never stopped.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this is easy to misdiagnose
&lt;/h2&gt;

&lt;p&gt;Many React Native performance issues are visible immediately.&lt;/p&gt;

&lt;p&gt;A heavy component may:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;render slowly&lt;/li&gt;
&lt;li&gt;block the JavaScript thread&lt;/li&gt;
&lt;li&gt;display a large list inefficiently&lt;/li&gt;
&lt;li&gt;perform expensive calculations during render&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Memory leaks often behave differently.&lt;/p&gt;

&lt;p&gt;They usually appear gradually.&lt;/p&gt;

&lt;p&gt;The first navigation works correctly.&lt;/p&gt;

&lt;p&gt;The second also looks fine.&lt;/p&gt;

&lt;p&gt;But after opening and closing the same screen multiple times, the application may start doing more work than expected.&lt;/p&gt;

&lt;p&gt;That gradual degradation makes the issue look like a rendering problem, even when the rendering logic has not changed.&lt;/p&gt;

&lt;p&gt;The important question is not only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is React rendering now?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You also need to ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the previous screen still doing?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A simple failure scenario
&lt;/h2&gt;

&lt;p&gt;Consider a screen that subscribes to an event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DeviceEventEmitter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;StatusScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;DeviceEventEmitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;device-status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Device status:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this code looks harmless.&lt;/p&gt;

&lt;p&gt;The subscription is created only once for each mount because the dependency array is empty.&lt;/p&gt;

&lt;p&gt;But there is no cleanup.&lt;/p&gt;

&lt;p&gt;Each time the screen mounts, another listener is registered.&lt;/p&gt;

&lt;p&gt;After navigating to the screen five times, the same event may trigger five callbacks.&lt;/p&gt;

&lt;p&gt;The screen may not look broken, but the application is now doing unnecessary work.&lt;/p&gt;

&lt;p&gt;A safer version removes the listener during cleanup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DeviceEventEmitter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;StatusScreen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DeviceEventEmitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;device-status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Device status:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important improvement is not in the render function.&lt;/p&gt;

&lt;p&gt;It is in the lifecycle ownership.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common sources of memory leaks in React Native
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Event listeners without cleanup
&lt;/h3&gt;

&lt;p&gt;Listeners are one of the most common causes of duplicated work.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DeviceEventEmitter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NativeEventEmitter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;keyboard listeners&lt;/li&gt;
&lt;li&gt;dimension listeners&lt;/li&gt;
&lt;li&gt;custom event buses&lt;/li&gt;
&lt;li&gt;navigation listeners&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common mistake is to register the listener correctly but forget to unsubscribe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keyboardDidShow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;handleKeyboardShow&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every listener should have a clear cleanup path.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Timers that survive screen unmount
&lt;/h3&gt;

&lt;p&gt;Intervals are especially risky because they continue executing until explicitly cleared.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intervalId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;refreshStatus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intervalId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without cleanup, each screen visit may create another interval.&lt;/p&gt;

&lt;p&gt;The result may be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate API requests&lt;/li&gt;
&lt;li&gt;repeated state updates&lt;/li&gt;
&lt;li&gt;unnecessary battery usage&lt;/li&gt;
&lt;li&gt;unexpected native module calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code may still appear correct during a short test session.&lt;/p&gt;

&lt;p&gt;The problem becomes visible only after repeated use.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. AppState and NetInfo subscriptions
&lt;/h3&gt;

&lt;p&gt;App lifecycle and connectivity listeners often live close to application-level logic.&lt;/p&gt;

&lt;p&gt;That makes ownership less obvious.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;AppState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;handleAppStateChange&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NetInfo has a similar subscription pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NetInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connected:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isConnected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These subscriptions can be valid at the application level.&lt;/p&gt;

&lt;p&gt;The problem starts when the same global-style listener is created inside multiple screens or components.&lt;/p&gt;

&lt;p&gt;Before registering one, decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;should this listener exist once for the app?&lt;/li&gt;
&lt;li&gt;once per navigator?&lt;/li&gt;
&lt;li&gt;once per screen?&lt;/li&gt;
&lt;li&gt;once per active feature?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cleanup strategy depends on the ownership boundary.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Native module callbacks registered multiple times
&lt;/h3&gt;

&lt;p&gt;Native integration makes memory leaks harder to diagnose because the work may continue outside React.&lt;/p&gt;

&lt;p&gt;A native module may:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;emit Bluetooth events&lt;/li&gt;
&lt;li&gt;report sensor data&lt;/li&gt;
&lt;li&gt;receive location updates&lt;/li&gt;
&lt;li&gt;send foreground service events&lt;/li&gt;
&lt;li&gt;notify JavaScript about background tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the JavaScript side registers the callback repeatedly, the native module may continue sending events to multiple active listeners.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NativeEventEmitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MyNativeModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;measurement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;handleMeasurement&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For native integrations, also verify whether cleanup is required on the native side.&lt;/p&gt;

&lt;p&gt;Removing the JavaScript listener may not be enough if the native service itself keeps running.&lt;/p&gt;

&lt;p&gt;Sometimes cleanup also requires calling methods such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;MyNativeModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopMonitoring&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The correct lifecycle may therefore look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;MyNativeModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startMonitoring&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NativeEventEmitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MyNativeModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;measurement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;handleMeasurement&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;MyNativeModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopMonitoring&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why memory ownership matters more than simply calling &lt;code&gt;remove()&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Async work completing after unmount
&lt;/h3&gt;

&lt;p&gt;Async operations do not always create a permanent memory leak, but they can still produce stale work and unnecessary updates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fetchProfile&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the user leaves the screen before the request finishes, the callback may run after unmount.&lt;/p&gt;

&lt;p&gt;A simple guard can prevent stale updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchProfile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;loadProfile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;isActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For network requests, cancellation is often better when supported.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;fetch&lt;/code&gt;, you can use &lt;code&gt;AbortController&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;setProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;loadProfile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids unnecessary work instead of only ignoring the result.&lt;/p&gt;




&lt;h2&gt;
  
  
  The ownership questions every subscription should answer
&lt;/h2&gt;

&lt;p&gt;For every listener, timer, subscription or callback, identify four things.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Where is it created?
&lt;/h3&gt;

&lt;p&gt;Is it created inside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a screen&lt;/li&gt;
&lt;li&gt;a custom hook&lt;/li&gt;
&lt;li&gt;a context provider&lt;/li&gt;
&lt;li&gt;a navigation container&lt;/li&gt;
&lt;li&gt;an app-level service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tells you where cleanup should probably live.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Who owns it?
&lt;/h3&gt;

&lt;p&gt;Ownership should be explicit.&lt;/p&gt;

&lt;p&gt;A screen-level listener should normally disappear with the screen.&lt;/p&gt;

&lt;p&gt;An application-level listener may intentionally survive navigation.&lt;/p&gt;

&lt;p&gt;The problem is not long-lived work itself.&lt;/p&gt;

&lt;p&gt;The problem is long-lived work without clear ownership.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. When is it removed?
&lt;/h3&gt;

&lt;p&gt;Do not rely on assumptions.&lt;/p&gt;

&lt;p&gt;The cleanup path should be visible and testable.&lt;/p&gt;

&lt;p&gt;For React components, this often means returning a cleanup function from &lt;code&gt;useEffect&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createResource&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Can it be registered more than once?
&lt;/h3&gt;

&lt;p&gt;This question catches many production issues.&lt;/p&gt;

&lt;p&gt;A listener may be duplicated because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the component mounts repeatedly&lt;/li&gt;
&lt;li&gt;the effect dependency changes&lt;/li&gt;
&lt;li&gt;a navigation listener is registered during focus&lt;/li&gt;
&lt;li&gt;a retry flow reinitialises the subscription&lt;/li&gt;
&lt;li&gt;a native module setup method is called more than once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always verify whether the registration is idempotent.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;useEffect&lt;/code&gt; dependencies can create hidden duplication
&lt;/h2&gt;

&lt;p&gt;A cleanup function alone does not guarantee correct behaviour.&lt;/p&gt;

&lt;p&gt;Consider this effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleEvent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleEvent&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;handleEvent&lt;/code&gt; is recreated on every render, the effect may unsubscribe and resubscribe repeatedly.&lt;/p&gt;

&lt;p&gt;That may be valid, but it may also create unnecessary churn.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;useCallback&lt;/code&gt; when stable function identity is important:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleEvent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleEvent&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The debugging question is not simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this effect have cleanup?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How often does this effect run, and how often does it recreate the resource?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Focus listeners require extra care
&lt;/h2&gt;

&lt;p&gt;React Navigation screens may remain mounted even when they are not focused.&lt;/p&gt;

&lt;p&gt;That means screen lifecycle and focus lifecycle are not always the same.&lt;/p&gt;

&lt;p&gt;If work should run only while the screen is active, &lt;code&gt;useFocusEffect&lt;/code&gt; may be a better fit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFocusEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-navigation/native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;useFocusEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;subscribeToLiveData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures the subscription is active only while the screen is focused.&lt;/p&gt;

&lt;p&gt;Use this carefully.&lt;/p&gt;

&lt;p&gt;If the subscription should survive temporary navigation changes, screen-level focus cleanup may be too aggressive.&lt;/p&gt;

&lt;p&gt;The correct decision depends on ownership.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why render optimisation alone may not help
&lt;/h2&gt;

&lt;p&gt;Suppose you apply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;React.memo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useMemo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useCallback&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;list virtualisation&lt;/li&gt;
&lt;li&gt;smaller component trees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These changes can improve rendering.&lt;/p&gt;

&lt;p&gt;But they do not remove:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;old listeners&lt;/li&gt;
&lt;li&gt;duplicate timers&lt;/li&gt;
&lt;li&gt;native callbacks&lt;/li&gt;
&lt;li&gt;background services&lt;/li&gt;
&lt;li&gt;stale async tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may make the current screen render faster while previous screens continue consuming resources.&lt;/p&gt;

&lt;p&gt;That is why profiling only render behaviour can give an incomplete picture.&lt;/p&gt;

&lt;p&gt;Render profiling tells you what React is doing now.&lt;/p&gt;

&lt;p&gt;Lifecycle debugging tells you what earlier components may still be doing.&lt;/p&gt;

&lt;p&gt;You need both.&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical debugging process
&lt;/h2&gt;

&lt;p&gt;When a React Native app becomes slower after repeated navigation, use a repeatable process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Reproduce the degradation
&lt;/h3&gt;

&lt;p&gt;Do not test only from a fresh launch.&lt;/p&gt;

&lt;p&gt;Repeat the same flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;open the screen&lt;/li&gt;
&lt;li&gt;leave the screen&lt;/li&gt;
&lt;li&gt;open it again&lt;/li&gt;
&lt;li&gt;repeat several times&lt;/li&gt;
&lt;li&gt;trigger the same event or action&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Look for callbacks that increase with every cycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add temporary registration logs
&lt;/h3&gt;

&lt;p&gt;Log when resources are created and removed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Registering device listener&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;registerDeviceListener&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Removing device listener&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each registration, you should see a corresponding cleanup when expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Count callback executions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callbackCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;callbackCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Callback count:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callbackCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a single event triggers multiple callbacks after repeated navigation, duplicate listeners are likely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Inspect timers and polling
&lt;/h3&gt;

&lt;p&gt;Search for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;setInterval&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setTimeout&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;recursive polling&lt;/li&gt;
&lt;li&gt;retry loops&lt;/li&gt;
&lt;li&gt;animation loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Confirm that each one is cancelled correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Inspect native services
&lt;/h3&gt;

&lt;p&gt;Check whether native modules continue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scanning&lt;/li&gt;
&lt;li&gt;observing&lt;/li&gt;
&lt;li&gt;polling&lt;/li&gt;
&lt;li&gt;emitting events&lt;/li&gt;
&lt;li&gt;tracking location&lt;/li&gt;
&lt;li&gt;maintaining sockets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JavaScript cleanup may not stop native work automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Profile memory and activity
&lt;/h3&gt;

&lt;p&gt;Use platform tools when the issue is not obvious.&lt;/p&gt;

&lt;p&gt;For Android:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Android Studio Profiler&lt;/li&gt;
&lt;li&gt;Memory Profiler&lt;/li&gt;
&lt;li&gt;CPU Profiler&lt;/li&gt;
&lt;li&gt;Logcat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For iOS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Xcode Instruments&lt;/li&gt;
&lt;li&gt;Allocations&lt;/li&gt;
&lt;li&gt;Leaks&lt;/li&gt;
&lt;li&gt;Time Profiler&lt;/li&gt;
&lt;li&gt;Energy Log&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also use React Native tooling where useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React DevTools&lt;/li&gt;
&lt;li&gt;Hermes profiling&lt;/li&gt;
&lt;li&gt;Flipper, where supported by the project setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not only to see memory growth.&lt;/p&gt;

&lt;p&gt;Also check whether the number of callbacks, subscriptions or native operations grows with each navigation cycle.&lt;/p&gt;




&lt;h2&gt;
  
  
  A cleanup checklist for code review
&lt;/h2&gt;

&lt;p&gt;When reviewing React Native lifecycle code, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does every listener have an unsubscribe path?&lt;/li&gt;
&lt;li&gt;Does every interval have a &lt;code&gt;clearInterval&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Does every timeout have a &lt;code&gt;clearTimeout&lt;/code&gt; where required?&lt;/li&gt;
&lt;li&gt;Does every native subscription expose disposal?&lt;/li&gt;
&lt;li&gt;Does every background service have a stop method?&lt;/li&gt;
&lt;li&gt;Can this effect register more than once?&lt;/li&gt;
&lt;li&gt;Should this resource follow mount lifecycle or focus lifecycle?&lt;/li&gt;
&lt;li&gt;Can async work be cancelled?&lt;/li&gt;
&lt;li&gt;Can stale results update state?&lt;/li&gt;
&lt;li&gt;Is this subscription owned by the correct architectural layer?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This checklist is more useful than asking only whether &lt;code&gt;useEffect&lt;/code&gt; has a return function.&lt;/p&gt;

&lt;p&gt;Cleanup is not just syntax.&lt;/p&gt;

&lt;p&gt;It is an ownership decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  A stronger engineering rule
&lt;/h2&gt;

&lt;p&gt;Before optimising rendering, check whether the screen leaves behind any long-lived work.&lt;/p&gt;

&lt;p&gt;For every subscription, answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Where is it created?&lt;/li&gt;
&lt;li&gt;Who owns it?&lt;/li&gt;
&lt;li&gt;When is it removed?&lt;/li&gt;
&lt;li&gt;Can it be registered more than once?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A component may render perfectly and still damage performance over time.&lt;/p&gt;

&lt;p&gt;The leak often exists outside the visible render path.&lt;/p&gt;

&lt;p&gt;That is why repeated navigation is such an important test.&lt;/p&gt;

&lt;p&gt;A fresh launch shows whether the screen works.&lt;/p&gt;

&lt;p&gt;Repeated use shows whether the lifecycle works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;Not every slow React Native screen needs render optimisation.&lt;/p&gt;

&lt;p&gt;Sometimes the screen is slow because previous screens are still alive through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;listeners&lt;/li&gt;
&lt;li&gt;timers&lt;/li&gt;
&lt;li&gt;subscriptions&lt;/li&gt;
&lt;li&gt;native callbacks&lt;/li&gt;
&lt;li&gt;background tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Render profiling tells you what React is doing now.&lt;/p&gt;

&lt;p&gt;Lifecycle cleanup tells you what old work never stopped.&lt;/p&gt;

&lt;p&gt;When a React Native app becomes slower after repeated navigation, which cleanup path do you inspect first?&lt;/p&gt;




</description>
      <category>reactnative</category>
      <category>javascript</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Add Web Search Tool to AI Agent | Tool Calling Explained | Microsoft Foundry AI Agent Tutorial #6</title>
      <dc:creator>Subrata Kumar Das</dc:creator>
      <pubDate>Wed, 24 Jun 2026 16:20:56 +0000</pubDate>
      <link>https://dev.to/subraatakumar/add-web-search-tool-to-ai-agent-tool-calling-explained-microsoft-foundry-ai-agent-tutorial-6-2f3j</link>
      <guid>https://dev.to/subraatakumar/add-web-search-tool-to-ai-agent-tool-calling-explained-microsoft-foundry-ai-agent-tutorial-6-2f3j</guid>
      <description></description>
      <category>ai</category>
      <category>subraatakumar</category>
    </item>
    <item>
      <title>Deploy Your First AI Model in Microsoft Foundry | System Prompts Explained | AI Agent Course #5</title>
      <dc:creator>Subrata Kumar Das</dc:creator>
      <pubDate>Wed, 24 Jun 2026 16:18:12 +0000</pubDate>
      <link>https://dev.to/subraatakumar/deploy-your-first-ai-model-in-microsoft-foundry-system-prompts-explained-ai-agent-course-5-475p</link>
      <guid>https://dev.to/subraatakumar/deploy-your-first-ai-model-in-microsoft-foundry-system-prompts-explained-ai-agent-course-5-475p</guid>
      <description></description>
      <category>ai</category>
      <category>azure</category>
      <category>subraatakumar</category>
    </item>
    <item>
      <title>Microsoft Foundry Setup Tutorial | Create Your First AI Agent Project (Azure AI Foundry Course) #4</title>
      <dc:creator>Subrata Kumar Das</dc:creator>
      <pubDate>Wed, 24 Jun 2026 16:16:35 +0000</pubDate>
      <link>https://dev.to/subraatakumar/microsoft-foundry-setup-tutorial-create-your-first-ai-agent-project-azure-ai-foundry-course-4-28ge</link>
      <guid>https://dev.to/subraatakumar/microsoft-foundry-setup-tutorial-create-your-first-ai-agent-project-azure-ai-foundry-course-4-28ge</guid>
      <description></description>
    </item>
    <item>
      <title>What is an AI Agent? AI Agent vs Chatbot Explained | Microsoft Foundry Course #3</title>
      <dc:creator>Subrata Kumar Das</dc:creator>
      <pubDate>Wed, 24 Jun 2026 16:14:54 +0000</pubDate>
      <link>https://dev.to/subraatakumar/what-is-an-ai-agent-ai-agent-vs-chatbot-explained-microsoft-foundry-course-3-2fjd</link>
      <guid>https://dev.to/subraatakumar/what-is-an-ai-agent-ai-agent-vs-chatbot-explained-microsoft-foundry-course-3-2fjd</guid>
      <description></description>
      <category>ai</category>
      <category>azure</category>
      <category>beginners</category>
      <category>subrata</category>
    </item>
  </channel>
</rss>
