<?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: Kevin Coto🚀💡</title>
    <description>The latest articles on DEV Community by Kevin Coto🚀💡 (@kevincoto).</description>
    <link>https://dev.to/kevincoto</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%2F632883%2Ff79fe4de-f87b-420b-a70c-3c9d6cb7e9fa.png</url>
      <title>DEV Community: Kevin Coto🚀💡</title>
      <link>https://dev.to/kevincoto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kevincoto"/>
    <language>en</language>
    <item>
      <title>Building a LINE First Fitness Agent with AI Cost Gates</title>
      <dc:creator>Kevin Coto🚀💡</dc:creator>
      <pubDate>Wed, 15 Jul 2026 01:04:49 +0000</pubDate>
      <link>https://dev.to/kevincoto/building-a-line-first-fitness-agent-with-ai-cost-gates-8e5</link>
      <guid>https://dev.to/kevincoto/building-a-line-first-fitness-agent-with-ai-cost-gates-8e5</guid>
      <description>&lt;h2&gt;
  
  
  It Started in Thailand
&lt;/h2&gt;

&lt;p&gt;I built Fit Homie for LINE because that is what everyone in Thailand uses. If you want to reach people in Bangkok, you build on LINE. It is not a choice, it is the reality of the market.&lt;/p&gt;

&lt;p&gt;The idea was simple. Snap a photo of your meal, send it to the bot, get back the nutritional breakdown. No app downloads, no complex logging. Just open the chat app you already use and take a picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Real Users
&lt;/h2&gt;

&lt;p&gt;I tested it with my ex girlfriend and a group of friends. They got hooked. They were sending photos of every meal, comparing nutrition, tracking their progress. It was the first time I saw people actually stick with a fitness tool.&lt;/p&gt;

&lt;p&gt;But then they started noticing the cracks. What if they wanted to log a meal on a desktop? What if they wanted to share their progress with someone who did not use LINE? What if LINE went down?&lt;/p&gt;

&lt;p&gt;The chat app was both the reason it worked and the reason it could not scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost Problem Nobody Thinks About
&lt;/h2&gt;

&lt;p&gt;Before I launched, I calculated what it would cost if people actually used this thing. Every meal photo meant an AI inference call. If a hundred users logged three meals a day, that was three hundred API calls daily. At scale, the costs would eat any possible revenue.&lt;/p&gt;

&lt;p&gt;I built a five layer cost optimization pipeline. Exact match cache for the same photo. Fuzzy match for similar meals. A precomputed database of ten thousand common foods. A local LLM fallback before hitting the paid API. Rate limiting and batching for the paid tier.&lt;/p&gt;

&lt;p&gt;This cut AI costs by roughly eighty percent compared to calling the API directly every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Chat Bot to Mobile App
&lt;/h2&gt;

&lt;p&gt;The feedback from early users was clear. The concept was right but the delivery was limited by the platform. People wanted a real app. They wanted to see charts, set goals, track progress over time. Things a chat bot cannot do well.&lt;/p&gt;

&lt;p&gt;So Fit Homie is now becoming a mobile app. The LINE bot still exists and handles quick meal logging. The app adds the depth that the chat interface cannot provide. Progress tracking, nutrition analytics, goal setting, history.&lt;/p&gt;

&lt;p&gt;The bot is the front door. The app is the home.&lt;/p&gt;

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

&lt;p&gt;Django backend with LINE Messaging API. Gemini AI for food recognition. Next.js dashboard for analytics. Stripe for subscriptions. PostgreSQL for everything else.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>django</category>
      <category>line</category>
      <category>ai</category>
    </item>
    <item>
      <title>A Polyglot Feedback Platform: Django, Next.js, and Elixir Working Together</title>
      <dc:creator>Kevin Coto🚀💡</dc:creator>
      <pubDate>Wed, 15 Jul 2026 01:03:53 +0000</pubDate>
      <link>https://dev.to/kevincoto/a-polyglot-feedback-platform-django-nextjs-and-elixir-working-together-b9c</link>
      <guid>https://dev.to/kevincoto/a-polyglot-feedback-platform-django-nextjs-and-elixir-working-together-b9c</guid>
      <description>&lt;h2&gt;
  
  
  The Waiter Who Wanted to Give Feedback
&lt;/h2&gt;

&lt;p&gt;I worked as a waiter. I also love food. That combination gave me a weird perspective on restaurants. I saw how much effort went into the food and service, but I also saw the gap between what customers thought and what restaurant owners knew.&lt;/p&gt;

&lt;p&gt;Sometimes I wanted to tell a restaurant about an issue I noticed. Not to complain publicly, not to hurt their reputation. Just to help them improve. But there was no easy way to do that. Either I told a manager directly which felt confrontational, or I left a public review which felt destructive.&lt;/p&gt;

&lt;p&gt;I wanted a middle ground. A way to give feedback that reached the right people without the public shaming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Languages for Three Jobs
&lt;/h2&gt;

&lt;p&gt;The architecture was intentional. Each piece of the system has different needs and I picked the best tool for each one.&lt;/p&gt;

&lt;p&gt;Django handles the backend and the admin interface. It has the most mature ORM I have used and the admin panel is ready to go out of the box. For a system that needs to manage restaurants, feedback, and users, Django was the obvious choice.&lt;/p&gt;

&lt;p&gt;Next.js powers the customer facing form and the business dashboard. Restaurants in Bangkok serve a multilingual customer base. The frontend needed to handle multiple languages elegantly and Next.js with SSR delivers a fast experience on any device.&lt;/p&gt;

&lt;p&gt;Elixir with Oban handles background jobs. Email notifications, weekly digest generation, analytics aggregation. Oban makes job processing simple and reliable. If a job fails it retries. If traffic spikes it queues. The concurrency model handles everything without breaking a sweat.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment It Worked
&lt;/h2&gt;

&lt;p&gt;The first restaurant that used KraowKao saw real feedback come in. Not the usual public reviews that are either five stars or one star with no middle ground. Structured, honest feedback about specific things.&lt;/p&gt;

&lt;p&gt;They got excited because they could improve their service before it turned into a public complaint. A customer mentioned the music was too loud and they adjusted it the same day. Another mentioned a dish was undersalted and the chef fixed it for the next order.&lt;/p&gt;

&lt;p&gt;That is the whole point. Not to replace public reviews, but to give restaurants a chance to fix things before those reviews happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;p&gt;Customers scan a QR code on the table and answer a quick structured form. No account needed, no app download. The feedback goes directly to the restaurant dashboard where they can respond and track trends over time. Everything is private between the customer and the business.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech
&lt;/h2&gt;

&lt;p&gt;Django REST Framework for the API. Next.js for the multilingual frontend. Elixir with Oban for background job processing. PostgreSQL for everything.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>django</category>
      <category>elixir</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Building an AI Accountability Coach That Knows Your Habits</title>
      <dc:creator>Kevin Coto🚀💡</dc:creator>
      <pubDate>Wed, 15 Jul 2026 01:03:50 +0000</pubDate>
      <link>https://dev.to/kevincoto/building-an-ai-accountability-coach-that-knows-your-habits-28fh</link>
      <guid>https://dev.to/kevincoto/building-an-ai-accountability-coach-that-knows-your-habits-28fh</guid>
      <description>&lt;h2&gt;
  
  
  I Wanted to Fix My Calendar
&lt;/h2&gt;

&lt;p&gt;I had a problem. My calendar was chaotic. Meetings scattered across the day, deep work interrupted, and no easy way to see what my week actually looked like. I wanted something that could help me organize my time without me having to think about it.&lt;/p&gt;

&lt;p&gt;That is where PIA Coach started. Not as a grand AI experiment. Just as a tool to help me manage my own calendar better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning Patterns Over Time
&lt;/h2&gt;

&lt;p&gt;The first version was simple. It tracked when I completed tasks and when I procrastinated. Over time it started noticing patterns. I was most productive in the mornings after coffee. I procrastinated hardest around 3pm. On days after bad sleep, I struggled to start anything.&lt;/p&gt;

&lt;p&gt;I built the circadian inference system to learn these patterns automatically. It watches when you complete tasks, when you start them, when you avoid them. After enough data, it knows when to push you and when to back off.&lt;/p&gt;

&lt;p&gt;The key insight is that productivity is not about willpower. It is about timing. If you try to do deep work at 3pm when your energy is low, you will fail. PIA learns your energy cycles and schedules around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment It Got Real
&lt;/h2&gt;

&lt;p&gt;Several times PIA called me out and I actually listened. It sent a message saying "you have been avoiding that task for three days. You have 30 minutes before your next meeting. Start now."&lt;/p&gt;

&lt;p&gt;The reason I listened was not because the bot was smart. It was because it was right. It had been tracking me long enough to know my patterns better than I did. When it told me I was procrastinating, I could not argue because the data was there.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;PIA runs on Telegram with a Python backend. DeepSeek powers the coaching conversations. The behavioral profiling engine tracks every interaction and builds a model of your motivation patterns. Google Calendar integration gives it awareness of your schedule.&lt;/p&gt;

&lt;p&gt;The plugin architecture means new coaching modules can be added without touching the core. Each plugin handles a specific area. Habit tracking, deep work sessions, procrastination detection. The core engine learns from all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes It Different
&lt;/h2&gt;

&lt;p&gt;Most productivity tools are reactive. You set a reminder and it goes off at the time you chose. PIA is proactive. It knows when you are about to procrastinate and steps in before you do. It adapts its tone based on what works for you. Some people need tough love. Others need encouragement. PIA figures out which one you respond to.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>python</category>
      <category>telegram</category>
      <category>ai</category>
    </item>
    <item>
      <title>Building a Multi Platform AI Budget Coach</title>
      <dc:creator>Kevin Coto🚀💡</dc:creator>
      <pubDate>Wed, 15 Jul 2026 01:03:20 +0000</pubDate>
      <link>https://dev.to/kevincoto/building-a-multi-platform-ai-budget-coach-1f72</link>
      <guid>https://dev.to/kevincoto/building-a-multi-platform-ai-budget-coach-1f72</guid>
      <description>&lt;h2&gt;
  
  
  The Problem Was Me
&lt;/h2&gt;

&lt;p&gt;I was making more money but my savings were not going up. Every raise just meant I spent more. I tried every budgeting app on the market. They all worked for a week then I forgot about them.&lt;/p&gt;

&lt;p&gt;The pattern was always the same. Download app, enter expenses for three days, get bored, uninstall. The apps required too much work. I had to open them, navigate menus, fill forms. When I was tired after work, I was not going to do that.&lt;/p&gt;

&lt;p&gt;I realized the problem was not my spending. It was the tools. If budgeting required effort, I was not going to do it consistently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet People Where They Are
&lt;/h2&gt;

&lt;p&gt;I wanted to build something that required zero effort. No app to open, no dashboard to check, no forms to fill. Just send a message like you already do hundreds of times a day.&lt;/p&gt;

&lt;p&gt;But people use different apps. In some countries everyone is on WhatsApp. In others it is Telegram. Developers live on Discord. If I picked one platform, I would miss everyone else.&lt;/p&gt;

&lt;p&gt;So I built for all three. Same backend, same AI, same budget logic. The only difference is the messaging adapter on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment It Clicked
&lt;/h2&gt;

&lt;p&gt;The first time someone typed "spent 45 on dinner" and the bot replied with their remaining budget, their reaction was genuine surprise. It felt like magic because there was nothing to learn. No onboarding, no tutorial, no setup. Just type what you spent and it works.&lt;/p&gt;

&lt;p&gt;That is when I knew the concept was right. Not because the technology was impressive, but because it disappeared. The user did not feel like they were using a budgeting tool. They were just texting a friend who happened to track their money.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;FastAPI handles the backend. Each messaging platform has its own adapter that normalizes messages into a common format. The NLP layer parses natural language and extracts amounts, categories, and intent. PostgreSQL stores everything.&lt;/p&gt;

&lt;p&gt;The user types "spent 45 on groceries" and the bot logs it, updates the category budget, and replies with a confirmation and remaining balance. No forms, no fields, no friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Is Now
&lt;/h2&gt;

&lt;p&gt;DIA works across Telegram, WhatsApp, and Discord. It handles expense logging, budget tracking, savings goals, and spending analytics delivered as weekly digests. The natural language understanding improves over time as it learns how each user talks about their money.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>fastapi</category>
      <category>telegram</category>
      <category>ai</category>
    </item>
    <item>
      <title>Building a Voice AI Receptionist for Restaurants</title>
      <dc:creator>Kevin Coto🚀💡</dc:creator>
      <pubDate>Wed, 15 Jul 2026 01:03:18 +0000</pubDate>
      <link>https://dev.to/kevincoto/building-a-voice-ai-receptionist-for-restaurants-4hjg</link>
      <guid>https://dev.to/kevincoto/building-a-voice-ai-receptionist-for-restaurants-4hjg</guid>
      <description>&lt;h2&gt;
  
  
  The Problem That Wouldnt Let Go
&lt;/h2&gt;

&lt;p&gt;I spent time in restaurants. Not as a consultant, not as a tech person observing from the corner. I worked in them. I saw how the phone rang during the lunch rush and nobody could pick it up because everyone was running food, seating guests, handling complaints.&lt;/p&gt;

&lt;p&gt;Big chains have the same problem as family owned spots. The person who answers the phone is also managing the floor. When they are taking a reservation, they are not helping the customer standing in front of them. When they are helping that customer, the phone rings and a potential booking walks away.&lt;/p&gt;

&lt;p&gt;I wanted to fix that one thing. Not build another POS system, not another reservation platform. Just let the restaurant answer every call without needing a dedicated receptionist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Integration Nightmare
&lt;/h2&gt;

&lt;p&gt;The hard part was never the AI itself. It was making everything talk to each other.&lt;/p&gt;

&lt;p&gt;A phone call comes in through Twilio. That needs to connect to Vapi for the voice agent. The voice agent needs to call my backend to check availability and book reservations. Tool calling needs to happen inside the call in real time without noticeable delay. The backend needs to update the database and send confirmation messages.&lt;/p&gt;

&lt;p&gt;Getting all of that to work together took longer than anything else. Webhooks from Twilio needed to reach Vapi at the right moment. Vapi needed to trigger tool calls on my FastAPI server while the caller was still talking. The timing had to be tight enough that the caller never noticed they were talking to a chain of APIs.&lt;/p&gt;

&lt;p&gt;I hit a point where I questioned whether it was worth it. Every platform has its own quirks, its own timeout limits, its own idea of how a webhook should look. Getting them to agree took weeks of trial and error.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment It Clicked
&lt;/h2&gt;

&lt;p&gt;Then it worked. A real call came in. The AI answered, asked the right questions, used tool calling to check available times, and wrote the reservation into the database. No human involved.&lt;/p&gt;

&lt;p&gt;Watching that first successful end to end flow changed something. It was not just that the technology worked. It was that the restaurant could have answered that call while short staffed during a lunch rush. The caller got their reservation. The staff stayed focused on the floor.&lt;/p&gt;

&lt;p&gt;That moment made all the integration headaches worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Is Now
&lt;/h2&gt;

&lt;p&gt;Vozko is still in beta. It has been tested in short one day operations to validate the flow. The AI handles reservations, menu questions, and hours of operation. When it cannot handle something, it escalates to a human through SMS.&lt;/p&gt;

&lt;p&gt;The next step is getting it into a restaurant for a full week of real operations. Let it take the pressure of the lunch rush and see how many calls it can handle before needing to escalate.&lt;/p&gt;

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

&lt;p&gt;FastAPI for the backend that handles Twilio webhooks and orchestrates Vapi. Twilio for the phone system itself. Vapi for the voice AI agent that talks to callers. PostgreSQL for reservations and call logs. Next.js for the management dashboard where restaurants can see call history and reservation data.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>fastapi</category>
      <category>voice</category>
      <category>python</category>
    </item>
    <item>
      <title>Building a Reusable Chat Infrastructure with DynamoDB Single Table Design</title>
      <dc:creator>Kevin Coto🚀💡</dc:creator>
      <pubDate>Wed, 15 Jul 2026 00:51:11 +0000</pubDate>
      <link>https://dev.to/kevincoto/building-a-reusable-chat-infrastructure-with-dynamodb-single-table-design-2k99</link>
      <guid>https://dev.to/kevincoto/building-a-reusable-chat-infrastructure-with-dynamodb-single-table-design-2k99</guid>
      <description>&lt;h2&gt;
  
  
  The Problem That Kept Coming Back
&lt;/h2&gt;

&lt;p&gt;Every project eventually needed chat. Customer support, collaboration, real time updates. I built chat for one project, then another, then another. Each time I wrote similar code with slightly different data models. It was wasteful.&lt;/p&gt;

&lt;p&gt;I decided to build chat infrastructure once and reuse it everywhere. A single DynamoDB table that could power real time messaging for any application.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Single Table Design
&lt;/h2&gt;

&lt;p&gt;The entire chat system lives in one DynamoDB table. The key structure is simple.&lt;/p&gt;

&lt;p&gt;The primary key is the conversation ID combined with a timestamp as the sort key. This lets me query all messages in a conversation in order. The global secondary index maps users to their conversations so I can show someone their full chat history with a single query.&lt;/p&gt;

&lt;p&gt;That is it. Two access patterns. One table. No joins, no complex queries, no denormalization headaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why DynamoDB
&lt;/h2&gt;

&lt;p&gt;PostgreSQL would have worked but it would have required connection pooling, read replicas at scale, and ongoing maintenance. DynamoDB needs none of that. You provision the throughput and it handles the rest.&lt;/p&gt;

&lt;p&gt;For chat, the access patterns are well known and stable. You always query by conversation or by user. Those patterns never change. DynamoDB rewards you when you know your access patterns upfront and design around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Time Delivery
&lt;/h2&gt;

&lt;p&gt;Messages written to DynamoDB trigger a stream event. A Lambda picks up the event and broadcasts the message to all connected WebSocket clients through API Gateway. The client receives the message without polling.&lt;/p&gt;

&lt;p&gt;The system handles disconnections gracefully. If a client drops, the message stays in DynamoDB and gets delivered when they reconnect.&lt;/p&gt;

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

&lt;p&gt;A reusable chat module that any application can integrate. The same infrastructure powers chat across multiple projects with zero changes to the core code. Each application gets its own conversation namespace but shares the same DynamoDB table, the same WebSocket endpoint, and the same broadcast logic.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>dynamodb</category>
      <category>architecture</category>
      <category>svelte</category>
    </item>
    <item>
      <title>Designing a Three Reviewer Consensus Platform for Digital Harm Reporting</title>
      <dc:creator>Kevin Coto🚀💡</dc:creator>
      <pubDate>Wed, 15 Jul 2026 00:50:41 +0000</pubDate>
      <link>https://dev.to/kevincoto/designing-a-three-reviewer-consensus-platform-for-digital-harm-reporting-573o</link>
      <guid>https://dev.to/kevincoto/designing-a-three-reviewer-consensus-platform-for-digital-harm-reporting-573o</guid>
      <description>&lt;h2&gt;
  
  
  Agency Work for the South African Government
&lt;/h2&gt;

&lt;p&gt;I was freelancing through an agency and ended up working on a platform for the South African government. Real411 is where citizens report digital harms. Disinformation, hate speech, online harassment. The kind of content that poisons public discourse.&lt;/p&gt;

&lt;p&gt;The responsibility was heavy. Every report needed to be handled carefully because getting it wrong meant either letting harmful content stay up or silencing legitimate speech. There was no room for error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Three Reviewers
&lt;/h2&gt;

&lt;p&gt;A single reviewer could be biased. Two reviewers could disagree and deadlock. Three gave us a system where consensus was possible without being impossible to reach.&lt;/p&gt;

&lt;p&gt;Each reviewer comes from a different category. Different sectors, different expertise. A legal expert, a technical expert, a community representative. They evaluate the same report independently and cannot see each other's decisions until all three have submitted.&lt;/p&gt;

&lt;p&gt;More importantly, reviewers from different categories cannot communicate outside the platform with each other. They do not know who the other reviewers are. They do not work for the same organization. This isolation prevents collusion and ensures each decision is truly independent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Problem
&lt;/h2&gt;

&lt;p&gt;We wanted to use AI to help reviewers by providing summaries and overviews of reports. But when you are dealing with sensitive content, AI introduces risks. What if the summary misses a crucial detail? What if the AI hallucinates something that influences the reviewer's decision?&lt;/p&gt;

&lt;p&gt;We had to be extremely careful about where we placed AI in the workflow. It could help triage and organize, but it could never make or suggest a decision. The human reviewers remained the sole authority on every case.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Workflow Works
&lt;/h2&gt;

&lt;p&gt;A report comes in and gets assigned to three reviewers from different categories. Each reviews independently. If at least two of three agree on the outcome, the decision is applied automatically. If there is no consensus, it escalates to a senior reviewer.&lt;/p&gt;

&lt;p&gt;Timeouts ensure no report gets stuck. If a reviewer does not respond within twenty four hours, the report is reassigned. The entire process is tracked and auditable.&lt;/p&gt;

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

&lt;p&gt;React frontend for the reviewer dashboard. Node.js API layer. PostgreSQL with row level security to ensure reviewers can only see their own assignments. AWS Step Functions to orchestrate the workflow. SQS and SNS for event driven processing between components.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>aws</category>
      <category>workflow</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Building a Real Time Sports Scoring Engine with WebSockets and DynamoDB Streams</title>
      <dc:creator>Kevin Coto🚀💡</dc:creator>
      <pubDate>Wed, 15 Jul 2026 00:49:11 +0000</pubDate>
      <link>https://dev.to/kevincoto/building-a-real-time-sports-scoring-engine-with-websockets-and-dynamodb-streams-5c96</link>
      <guid>https://dev.to/kevincoto/building-a-real-time-sports-scoring-engine-with-websockets-and-dynamodb-streams-5c96</guid>
      <description>&lt;h2&gt;
  
  
  Tennis Courts in South Africa
&lt;/h2&gt;

&lt;p&gt;The project started with tennis courts in South Africa. Local tournaments needed a way to display live scores that everyone could see. Not just on a central board, but on phones, on screens around the court, anywhere people wanted to follow the match.&lt;/p&gt;

&lt;p&gt;From there it grew to cover other sports. The core problem was the same regardless of the game. A point happens, the score changes, and everyone needs to see it instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why DynamoDB Was the Key
&lt;/h2&gt;

&lt;p&gt;I tried other approaches first. Relational databases could handle the data but could not guarantee the latency we needed. In memory stores were fast but added complexity for persistence.&lt;/p&gt;

&lt;p&gt;DynamoDB was the answer because it does one thing well. You ask for data by a key and you get it back in single digit milliseconds. No query optimization, no connection pooling, no read replicas. Just fast predictable access.&lt;/p&gt;

&lt;p&gt;The single table design meant every access pattern was a simple key lookup. Get all messages in a conversation. Get all conversations for a user. Paginate by timestamp. Each query was fast because the table was designed around the queries, not around the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Real Time Works
&lt;/h2&gt;

&lt;p&gt;When a score happens, it goes through API Gateway to a Lambda that writes it to DynamoDB. DynamoDB Streams captures the change and triggers another Lambda. That Lambda broadcasts the update to every connected WebSocket client.&lt;/p&gt;

&lt;p&gt;The entire pipeline from score event to spectator screen takes under a tenth of a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  It Worked Flawlessly
&lt;/h2&gt;

&lt;p&gt;The first time it ran during a real tournament, it just worked. Scores appeared on screens around the court instantly. People watching from home saw updates at the same time as people sitting courtside.&lt;/p&gt;

&lt;p&gt;No missed updates. No duplicate points. No delays. The system scaled from a single match to multiple courts without any degradation.&lt;/p&gt;

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

&lt;p&gt;AWS API Gateway WebSocket for real time connections. Lambda for score processing and broadcast logic. DynamoDB for persistence with single table design. DynamoDB Streams for change data capture. Svelte for the spectator displays and scoring controls.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>architecture</category>
      <category>realtime</category>
      <category>websocket</category>
    </item>
    <item>
      <title>Migrating a Production Platform from CDK to SST Without Downtime</title>
      <dc:creator>Kevin Coto🚀💡</dc:creator>
      <pubDate>Wed, 15 Jul 2026 00:48:21 +0000</pubDate>
      <link>https://dev.to/kevincoto/migrating-a-production-platform-from-cdk-to-sst-without-downtime-37ec</link>
      <guid>https://dev.to/kevincoto/migrating-a-production-platform-from-cdk-to-sst-without-downtime-37ec</guid>
      <description>&lt;h2&gt;
  
  
  When CDK Was Not Enough
&lt;/h2&gt;

&lt;p&gt;Restore Blue helps organizations manage coastal restoration projects. Millions of trees, complex tracking, real impact data. The infrastructure was built on AWS CDK and it worked. But as the platform grew, the limitations became hard to ignore.&lt;/p&gt;

&lt;p&gt;CDK is powerful but developing with it is slow. Every change requires a full deployment. There is no way to test a Lambda function locally with real infrastructure. You write code, deploy, wait, check logs, repeat.&lt;/p&gt;

&lt;p&gt;SST changed that. Live Lambda development meant I could see changes instantly. Resource bindings gave me type safe references between infrastructure and application code. The developer experience was significantly better.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Migration Strategy
&lt;/h2&gt;

&lt;p&gt;The key was running both stacks in parallel. The CDK stack kept serving production traffic while the SST stack was built and validated alongside it.&lt;/p&gt;

&lt;p&gt;Phase one was deploying the SST stack with the same resources as the existing CDK stack. Phase two was routing new traffic to SST resources. Phase three was validating that SST handled every workload correctly. Phase four was decommissioning the CDK stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tricky Parts
&lt;/h2&gt;

&lt;p&gt;DynamoDB tables needed to be recreated with SST naming conventions without losing data. That required a careful export and import process validated multiple times before the production cutover.&lt;/p&gt;

&lt;p&gt;Route53 records needed to switch from CDK to SST resources atomically. One wrong DNS change could take the entire platform offline.&lt;/p&gt;

&lt;p&gt;The team needed to coordinate feature freezes around migration windows. Every migration run needed to be scheduled, communicated, and executed cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Saved Us
&lt;/h2&gt;

&lt;p&gt;I ran the full migration in staging three times before attempting production. Each run uncovered something. A missing permission, a misconfigured environment variable, a Lambda function that referenced the old resource name.&lt;/p&gt;

&lt;p&gt;By the time we did the production migration, it was routine. Thirty minutes of work and the CDK stack was decommissioned. Zero downtime, zero data loss.&lt;/p&gt;

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

&lt;p&gt;Parallel stacks are safer than big bang migrations. Run both, validate the new one, then turn off the old one. Automate your validation. If you can test it in a script, test it in a script. And run the migration more times than you think you need to.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>architecture</category>
      <category>sst</category>
      <category>cdk</category>
    </item>
    <item>
      <title>From Dubai to Thailand: How I Landed a Remote Role at a South African Company</title>
      <dc:creator>Kevin Coto🚀💡</dc:creator>
      <pubDate>Wed, 15 Jul 2026 00:28:06 +0000</pubDate>
      <link>https://dev.to/kevincoto/from-dubai-to-thailand-how-i-found-remote-work-across-three-continents-5fc3</link>
      <guid>https://dev.to/kevincoto/from-dubai-to-thailand-how-i-found-remote-work-across-three-continents-5fc3</guid>
      <description>&lt;h2&gt;
  
  
  The Next Chapter
&lt;/h2&gt;

&lt;p&gt;When I left the waiter job and returned to engineering, I knew I wanted something different. Not just a different job, but a different way of working. The kind where your location does not limit the problems you can solve.&lt;/p&gt;

&lt;p&gt;I found that in Thailand, working for a South African company called Exonic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Bangkok
&lt;/h2&gt;

&lt;p&gt;After Dubai, I wanted somewhere with a lower cost of living where I could build runway while working remotely. Bangkok checks that box. The city is a hub for remote engineers. The internet is fast. The infrastructure works. The street food is better than any restaurant I have ever worked in.&lt;/p&gt;

&lt;p&gt;I arrived with a laptop and a clear goal: find a remote role where I could work on meaningful projects without being tied to a physical office.&lt;/p&gt;




&lt;h2&gt;
  
  
  Landing the Role at Exonic
&lt;/h2&gt;

&lt;p&gt;Exonic is a technology consulting company based in South Africa. They serve clients across multiple industries and geographies. When I found the opening, it matched exactly what I was looking for: full time remote, exposure to diverse projects, and the chance to work across the full stack.&lt;/p&gt;

&lt;p&gt;The interview process was practical. System design discussions, technical assessments focused on AWS and modern frontend frameworks, and conversations about how I approach end to end delivery.&lt;/p&gt;

&lt;p&gt;I got the offer and accepted it immediately.&lt;/p&gt;

&lt;p&gt;As a full time remote employee, I was embedded in Exonic's engineering team. My day to day involved building cloud native solutions for their clients, designing architectures on AWS, and shipping production systems across the entire stack. The team was distributed, and the work required communicating clearly across time zones.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three Continents Through One Company
&lt;/h2&gt;

&lt;p&gt;Exonic's client base spans the globe. Over my time there, I built production systems touching three different continents.&lt;/p&gt;

&lt;p&gt;One project was &lt;strong&gt;&lt;a href="https://staging-ai-scoring.exonicsolutions.com/" rel="noopener noreferrer"&gt;Scoring AI&lt;/a&gt;&lt;/strong&gt;, a voice enabled match scoring application for sports courts. Players start a match, share a link, and control the scoreboard using voice commands. I worked on the full stack: the real time scoring engine, voice command processing, the shareable match link flow, and the responsive display that works on phones, tablets, and court side screens. The stack was Svelte on the frontend with a Node backend deployed on AWS.&lt;/p&gt;

&lt;p&gt;Another project was &lt;strong&gt;My IBD Pal&lt;/strong&gt; for Ferring Malaysia, a healthcare application for patients with inflammatory bowel disease. This involved building patient facing interfaces and clinical backend systems, processing health data securely, and integrating with healthcare infrastructure in the Malaysian market. The project is protected and not publicly accessible.&lt;/p&gt;

&lt;p&gt;I also worked on &lt;strong&gt;Real411&lt;/strong&gt;, a digital complaints platform for Media Monitoring Africa in South Africa. The platform allows citizens to report digital offenses, track complaint status, and supports media regulation workflows. Full stack development, from the submission forms to the admin dashboard and trend analytics. This project is also under restricted access.&lt;/p&gt;

&lt;p&gt;I also worked on &lt;strong&gt;Real411&lt;/strong&gt;, a digital complaints platform for Media Monitoring Africa in South Africa. The platform allows citizens to report digital offenses, track complaint status, and supports media regulation workflows. Full stack development, from the submission forms to the admin dashboard and trend analytics.&lt;/p&gt;

&lt;p&gt;Each project had different business contexts, different regulatory requirements, and different user bases. The engineering principles remained the same: understand the domain, design the solution, ship it, document it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The AWS Solutions Architect Certification
&lt;/h2&gt;

&lt;p&gt;Working across multiple AWS projects for different clients made one thing clear: I needed a formal credential that backed up the experience. The AWS Solutions Architect certification is the industry standard for cloud architecture knowledge.&lt;/p&gt;

&lt;p&gt;I studied for six weeks. Not because the material is difficult, but because the scope is wide. Compute, storage, networking, databases, security, cost optimization. Each domain has multiple services and multiple trade offs.&lt;/p&gt;

&lt;p&gt;The exam is scenario based. You are given a business requirement and you choose the architecture that meets it. Not just which service, but why. The certification forces you to think in trade offs: cost versus performance, latency versus durability, managed versus self hosted.&lt;/p&gt;

&lt;p&gt;Passing it validated the patterns I had been applying in production for Exonic's clients. The theory matched the practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  End to End Ownership
&lt;/h2&gt;

&lt;p&gt;One of the best parts of the role was the scope. Exonic trusted me with full projects, not just isolated tickets.&lt;/p&gt;

&lt;p&gt;An end to end project looked like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Discovery. Understand the business domain. What problem are we solving? Who are the users? What are the constraints?&lt;/li&gt;
&lt;li&gt;Architecture. Choose the stack and design the system. Document the rationale.&lt;/li&gt;
&lt;li&gt;Implementation. Build it. Write tests. Set up CI/CD.&lt;/li&gt;
&lt;li&gt;Deployment. Put it in production. Configure monitoring and alerts.&lt;/li&gt;
&lt;li&gt;Handoff. Document everything. Train the team. Transfer ownership.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The AWS certification helped with step two. The experience working across different client contexts helped with step one. The discipline of shipping consistently helped with steps three through five.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changed
&lt;/h2&gt;

&lt;p&gt;When I was in Cuba, I was a good engineer with limited options. When I was in Dubai working as a waiter, I was someone starting from zero in a new country. When I landed in Bangkok and joined Exonic, I was someone who had learned that the ceiling is not set by your location.&lt;/p&gt;

&lt;p&gt;It is set by your ability to deliver.&lt;/p&gt;

&lt;p&gt;Employers do not care where you log in from. They care whether the code compiles, the deployments succeed, and the systems stay up. If you can do that consistently, you can work from anywhere.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Full time remote is different from freelancing.&lt;/strong&gt; Freelancing gives you flexibility. A full time remote role gives you stability, team structure, and exposure to larger projects. Both are valid, but they require different mindsets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A South African company hiring someone in Thailand to build for clients in Malaysia, South Africa, and beyond is not unusual anymore.&lt;/strong&gt; Geography is no longer a constraint. The companies that understand this win access to global talent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Specific projects teach more than generic ones.&lt;/strong&gt; Scoring AI taught me about real time voice processing on the web. My IBD Pal taught me about healthcare data compliance. Real411 taught me about building for media regulation workflows. Each project expanded my context, not just my tech stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Certifications validate what you already know.&lt;/strong&gt; They are not a replacement for experience. But they make it easier to get past the first screening call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full ownership changes how you build.&lt;/strong&gt; When you own the entire lifecycle, you design differently. You think about deployability from day one. You write documentation as you go. You build systems that do not require you to be on call forever.&lt;/p&gt;

&lt;p&gt;I started my career in Cuba. I worked as a waiter in Dubai. I moved to Thailand and joined a South African company building for clients across three continents. The path was not linear, but it was worth it.&lt;/p&gt;

</description>
      <category>career</category>
      <category>life</category>
      <category>aws</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Integrating Cloudflare Turnstile with SvelteKit Forms</title>
      <dc:creator>Kevin Coto🚀💡</dc:creator>
      <pubDate>Sun, 30 Mar 2025 15:29:59 +0000</pubDate>
      <link>https://dev.to/kevincoto/using-cloudflare-turnstile-with-sveltekit-a-simple-guide-398k</link>
      <guid>https://dev.to/kevincoto/using-cloudflare-turnstile-with-sveltekit-a-simple-guide-398k</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Bot protection is essential for any public facing form. Cloudflare Turnstile offers a privacy friendly alternative to traditional CAPTCHAs: no image grids, no traffic analysis, just a lightweight challenge that runs in the background.&lt;/p&gt;

&lt;p&gt;When you pair it with SvelteKit's &lt;code&gt;use:enhance&lt;/code&gt; for progressive form enhancement, you hit a subtle issue. Turnstile tokens are single use. After a successful submission, the old token is consumed. The next submission attempt fails because the widget still holds the spent token. The page does not reload (that is the whole point of &lt;code&gt;use:enhance&lt;/code&gt;), so the widget never gets a chance to issue a fresh one.&lt;/p&gt;

&lt;p&gt;This guide walks through a complete SvelteKit integration with server side validation, client side token refresh, and multiple submission support.&lt;/p&gt;




&lt;h2&gt;
  
  
  Server Side Validation
&lt;/h2&gt;

&lt;p&gt;Turnstile verification must happen on the backend. A client side check alone can be bypassed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend (&lt;code&gt;+page.server.ts&lt;/code&gt;)
&lt;/h3&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;SECRET_TURNSTILE_KEY&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;$env/static/private&lt;/span&gt;&lt;span class="dl"&gt;'&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;validateToken&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="kr"&gt;string&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;boolean&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;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;https://challenges.cloudflare.com/turnstile/v0/siteverify&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&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="s1"&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="s1"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SECRET_TURNSTILE_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;response&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="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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&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;formData&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&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;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cf-turnstile-response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&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;token&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&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;validateToken&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Verification failed. Please try again.&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="na"&gt;success&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="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Form submitted successfully.&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noting here.&lt;/p&gt;

&lt;p&gt;Turnstile returns the token as &lt;code&gt;cf-turnstile-response&lt;/code&gt; by default. That field name is configurable if you need to avoid collisions.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;siteverify&lt;/code&gt; endpoint expects URL encoded form data, not JSON. This is a common gotcha when using &lt;code&gt;fetch&lt;/code&gt; with the default JSON content type.&lt;/p&gt;

&lt;p&gt;Store the secret key in a &lt;code&gt;.env&lt;/code&gt; file as &lt;code&gt;SECRET_TURNSTILE_KEY&lt;/code&gt; and reference it through SvelteKit's &lt;code&gt;$env/static/private&lt;/code&gt; module. Never expose it to the client.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frontend Integration
&lt;/h2&gt;

&lt;p&gt;The client side uses the &lt;code&gt;svelte-turnstile&lt;/code&gt; package which wraps the Turnstile widget as a Svelte component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;svelte-turnstile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Frontend (&lt;code&gt;+page.svelte&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;Turnstile&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;svelte-turnstile&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;enhance&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;$app/forms&lt;/span&gt;&lt;span class="dl"&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;captchaKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&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;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$props&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;$effect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;captchaKey&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;form&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="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt; &lt;span class="na"&gt;use:enhance&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;#key&lt;/span&gt; &lt;span class="nx"&gt;captchaKey&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Turnstile&lt;/span&gt; &lt;span class="na"&gt;siteKey=&lt;/span&gt;&lt;span class="si"&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_TURNSTILE_SITEKEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;/key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why the Key Block Works
&lt;/h3&gt;

&lt;p&gt;SvelteKit's &lt;code&gt;use:enhance&lt;/code&gt; intercepts the form submission and updates the page without a full navigation. The Turnstile widget holds a token that has now been spent.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;{#key captchaKey}&lt;/code&gt; block tells Svelte to destroy and recreate its children whenever &lt;code&gt;captchaKey&lt;/code&gt; changes. Incrementing the counter inside the &lt;code&gt;$effect&lt;/code&gt; forces a fresh Turnstile widget to mount with a new token.&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;setTimeout&lt;/code&gt;, no mount/unmount races, just a counter increment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Environment Variables
&lt;/h2&gt;

&lt;p&gt;You need two keys from the Cloudflare Turnstile dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;VITE_TURNSTILE_SITEKEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0x4AAAAAA...    # public, exposed to browser&lt;/span&gt;
&lt;span class="py"&gt;SECRET_TURNSTILE_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0x4AAAAAA...       # private, server only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;VITE_&lt;/code&gt; prefix makes the variable available to client side Vite bundles. The un-prefixed version is loaded through &lt;code&gt;$env/static/private&lt;/code&gt; and never leaves the server.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Considerations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Token expiry.&lt;/strong&gt; Turnstile tokens expire after 300 seconds. If a user fills out a long form, the token may become invalid by the time they hit submit. Consider implementing a periodic refresh mechanism for forms with long completion times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limiting.&lt;/strong&gt; The &lt;code&gt;/siteverify&lt;/code&gt; endpoint does not have built in rate limiting. Implement your own per IP or per session limits to prevent abuse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessibility.&lt;/strong&gt; Turnstile runs a non interactive challenge by default. If the challenge fails, it falls back to a visible widget. Test this flow with keyboard only navigation and screen readers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple forms on one page.&lt;/strong&gt; Each Turnstile instance needs a unique widget ID. The &lt;code&gt;svelte-turnstile&lt;/code&gt; component handles this internally, but verify that your page layout does not share instances between forms.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Turnstile tokens are single use and require a fresh widget after each submission.&lt;/li&gt;
&lt;li&gt;Server side validation is mandatory. Never trust the client alone.&lt;/li&gt;
&lt;li&gt;The mount/unmount pattern in &lt;code&gt;$effect&lt;/code&gt; solves the token refresh problem cleanly.&lt;/li&gt;
&lt;li&gt;Environment variables keep your keys separated by environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup gives you a secure, bot protected form that works with SvelteKit's progressive enhancement.&lt;/p&gt;

</description>
      <category>svelte</category>
      <category>captcha</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>LeetCode 35: Search Insert Position. Binary Search vs Linear Scan</title>
      <dc:creator>Kevin Coto🚀💡</dc:creator>
      <pubDate>Fri, 14 Mar 2025 19:50:34 +0000</pubDate>
      <link>https://dev.to/kevincoto/yeah-leetcode-without-google-or-llm-part-1-35-search-insert-position-4gpl</link>
      <guid>https://dev.to/kevincoto/yeah-leetcode-without-google-or-llm-part-1-35-search-insert-position-4gpl</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Given a sorted array of distinct integers and a target value, return the index where the target would be inserted to maintain order. If the target already exists, return its index.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input: nums = [1, 3, 5, 6], target = 5
Output: 2

Input: nums = [1, 3, 5, 6], target = 2
Output: 1

Input: nums = [1, 3, 5, 6], target = 7
Output: 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is straightforward on the surface, but the constraints reveal its intent. The array is sorted, and the operation needs to complete efficiently.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Linear Solution
&lt;/h2&gt;

&lt;p&gt;A linear scan checks each element until we find the insertion point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;searchInsert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &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;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&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;i&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;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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 works but runs in O(n) time. For an array of 10,000 elements, the worst case iterates through all of them. The problem expects a solution that exploits the fact that the input is sorted.&lt;/p&gt;




&lt;h2&gt;
  
  
  Binary Search
&lt;/h2&gt;

&lt;p&gt;Binary search divides the search space in half at each step. It runs in O(log n) time, which means finding an element in a million entries takes at most 20 comparisons instead of a million.&lt;/p&gt;

&lt;p&gt;The algorithm maintains three pointers: &lt;code&gt;left&lt;/code&gt;, &lt;code&gt;right&lt;/code&gt;, and &lt;code&gt;mid&lt;/code&gt;. At each step we check the middle element. If it matches the target, we are done. If the target is smaller, we discard the right half. If larger, we discard the left half.&lt;/p&gt;

&lt;p&gt;When the loop ends without finding the target, &lt;code&gt;left&lt;/code&gt; points to where the target should be inserted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;searchInsert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&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;left&lt;/span&gt; &lt;span class="o"&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;let&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;right&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;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&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;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;target&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;mid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mid&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="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;left&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;h2&gt;
  
  
  Why the Insertion Point Works
&lt;/h2&gt;

&lt;p&gt;When the loop exits, &lt;code&gt;left&lt;/code&gt; has crossed &lt;code&gt;right&lt;/code&gt;. The element at &lt;code&gt;left&lt;/code&gt; is the first element greater than the target (or &lt;code&gt;left&lt;/code&gt; equals the array length if the target belongs at the end). This is exactly the insertion position.&lt;/p&gt;

&lt;p&gt;The invariant &lt;code&gt;nums[right] &amp;lt; target &amp;lt; nums[left]&lt;/code&gt; holds at the point of exit. Visualizing this helps. Try tracing through &lt;code&gt;[1, 3, 5, 6]&lt;/code&gt; with &lt;code&gt;target = 2&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: left=0, right=3, mid=1, nums[1]=3 &amp;gt; 2, go left
Step 2: left=0, right=0, mid=0, nums[0]=1 &amp;lt; 2, go right
Step 3: left=1, right=0, exit loop, return left=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The index 1 is where 2 fits between 1 and 3.&lt;/p&gt;




&lt;h2&gt;
  
  
  Edge Cases
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Target smaller than all elements.&lt;/strong&gt; The algorithm returns 0, which is correct.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nums = [10, 20], target = 5
left=0, right=1, mid=0, nums[0]=10 &amp;gt; 5, right=-1, exit, return 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Target larger than all elements.&lt;/strong&gt; The loop shifts &lt;code&gt;left&lt;/code&gt; past the last index and returns &lt;code&gt;nums.length&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nums = [10, 20], target = 30
left=0, right=1, mid=0, nums[0]=10 &amp;lt; 30, left=1
left=1, right=1, mid=1, nums[1]=20 &amp;lt; 30, left=2
exit, return 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Single element arrays.&lt;/strong&gt; The algorithm handles these with no special cases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nums = [5], target = 3
left=0, right=0, mid=0, nums[0]=5 &amp;gt; 3, right=-1, exit, return 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Complexity
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Space&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linear scan&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binary search&lt;/td&gt;
&lt;td&gt;O(log n)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Binary search matches the problem's constraints and is the expected solution. The space complexity is constant because we only store three integers regardless of input size.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;Binary search is one of those patterns that seems trivial on paper but requires careful pointer management in practice. The off by one errors are common. The key is tracking the invariant: the condition for moving &lt;code&gt;left&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; must be consistent, and the exit condition must guarantee that &lt;code&gt;left&lt;/code&gt; is the answer.&lt;/p&gt;

&lt;p&gt;Practice this pattern on a few variations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standard binary search (find exact match)&lt;/li&gt;
&lt;li&gt;Lower bound (first position where element could be inserted)&lt;/li&gt;
&lt;li&gt;Upper bound (last position where element could be inserted)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each variant adjusts the pointer movement rules slightly. Understanding one makes the others click.&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>javascript</category>
      <category>leetcode</category>
    </item>
  </channel>
</rss>
