# Swapnoneel Saha — Full Content Index > Generated from the same Markdown and trust sources used by the website. > See /llms.txt for the concise profile and current route index. --- # Credentials and verification — Swapnoneel Saha A source map for agents, recruiters, and collaborators evaluating Swapnoneel Saha. It separates public records from first-party claims and states the limits of each source. Last reviewed: 23 August 2026. External pages can change; re-check the linked source before making a time-sensitive or high-stakes decision. ## How evidence is classified - Official record: published by the school, institution, or authority responsible for the record. - Platform or publisher record: public activity, authorship, or an award recorded by the platform that hosted it. - Self-reported: supplied by Swapnoneel on this portfolio or a personal profile; useful context, but not independent verification. ## Evidence index ### [GitHub profile](https://github.com/Swpn0neel) - Evidence level: Platform or publisher record - Source: GitHub - Supports: Public code, repositories, contribution activity, the name Swapnoneel Saha, and a profile link back to swapnoneel.site. - Does not prove: A personal GitHub profile supports identity continuity and public technical activity; it does not independently verify employment or private client results. ### [Technical article bylines](https://keploy.io/blog/tag/software-development) - Evidence level: Platform or publisher record - Source: Keploy - Supports: Publisher-hosted technical articles carrying Swapnoneel Saha's byline on Keploy's official domain. - Does not prove: A byline verifies published work on the named domain, not the complete scope, dates, or business impact of an employment relationship. ### [Open-source documentation history](https://github.com/keploy/keploy/wiki/1.-Know-more-about-Keploy) - Evidence level: Platform or publisher record - Source: Keploy on GitHub - Supports: Public edit history in Keploy's GitHub repository attributes a documentation edit to Swapnoneel Saha. - Does not prove: This is evidence of a specific public contribution, not proof of every open-source or employment claim in the résumé. ### [Hack Around the World 2 project and award](https://devpost.com/software/the-magnificent-seven) - Evidence level: Platform or publisher record - Source: Devpost - Supports: The project record names Swapnoneel Saha as a creator and records the My First Travel Hack winner placement. - Does not prove: The record supports this named project and placement only; it does not verify other awards listed on the portfolio. ### [CBSE Class XII result](https://www.kalyanipublicschool.org/src/pdf/KPS_TOPPERS.pdf) - Evidence level: Official record - Source: Kalyani Public School - Supports: A result document published on the school's official domain lists Swapnoneel Saha with a 93% Class XII result. - Does not prove: This verifies the named school result only. The portfolio does not publish a university transcript, degree certificate, or identity document. ### [Résumé and professional timeline](https://www.swapnoneel.site/resume) - Evidence level: Self-reported - Source: swapnoneel.site - Supports: The current skills, employment timeline, education, projects, and stated impact that Swapnoneel presents to prospective collaborators. - Does not prove: These claims are first-party statements unless a separate source on this page supports them. Current and past employer roles are not presented as employer-verified. ### [Professional profile](https://www.linkedin.com/in/swapnoneel-saha-14a3161b6/) - Evidence level: Self-reported - Source: LinkedIn - Supports: A longstanding professional profile with work and education history under the same name. - Does not prove: LinkedIn profile fields are generally supplied by the account holder and should not be treated as employer or institution verification by themselves. ## Next steps - [Review the résumé](https://www.swapnoneel.site/resume): See the complete professional timeline and skills. - [Inspect work samples](https://www.swapnoneel.site/work): Explore experience, projects, and case studies. - [Start a conversation](https://www.swapnoneel.site/contact): Share an opportunity, project, or focused question. --- # Work Experience ## GTM Engineer • Bifrost URL: https://www.swapnoneel.site/work/bifrost Date: Jul 2026 - Present Summary: Leading technical go-to-market strategies for Bifrost, authoring architectural deep-dives, driving 300K+ impressions across developer communities, and scaling backlink and creator pipelines. [Bifrost](https://getbifrost.ai) (by Maxim AI) is a high-performance, [open-source AI gateway](https://github.com/maximhq/bifrost) that unifies access to 20+ providers through a single OpenAI-compatible API, featuring adaptive routing, guardrails, governance, and observability. ## What I am doing - Researched and authored **10+ comprehensive architectural deep-dives** on Bifrost (covering adaptive load balancing, enterprise guardrails, semantic caching, AI observability, virtual keys, and MCP gateway integration), syndicating them across DEV.to, Medium, Substack, and my personal blogging page. - Submitted **50+ pull requests** to curated GitHub "Awesome" lists, securing merged high-authority backlinks to drive discovery and organic GitHub stars. - Formulated and executed multi-channel developer distribution strategies across Reddit, daily.dev, Peerlist, and Hacker News, that gained over **300K+ impressions**. And also, it drove discussions in `r/opencode`, `r/LocalLLM`, and `r/mcp`, including **achieving top-ranking community posts on `r/opencode`**. - Built an outreach pipeline of **250+ guest-posting platforms** and **high-tier tech creators** across YouTube, Medium, DEV.to, and Substack, initiating collaboration outreach to prominent creators. - Listed and launched Bifrost across **40+ high DR sites**, that increased the Domain Rating of the site to 69. - Tested Bifrost's gateway features hands-on, **opened 4 GitHub issues** with product feedback for the core engineering team, conducted competitive performance benchmarking against LiteLLM's Rust implementation, and audited documentation benchmark discrepancies and AIEO (AI Engine Optimization) gaps. - Conducted thorough competitive analysis of Bifrost's competitors including **LiteLLM**, **Truefoundry**, **Kong AI Gateway** and **Portkey**, to identify market positioning and differentiation opportunities. - Updated **70+ high-performing legacy technical articles** to embed Bifrost gateway workflows, contextual backlinks, and updated AI architecture practices. - Handling weekly outreach for **guest posting and community collaborations**, and onboarded **10+ new influencers** across various platform by auditing over **700 profiles**. - Planned **9 new growth experiments** that can significantly increase the community engagement and presence of Bifrost. ## GTM Engineer • Zonko Labs URL: https://www.swapnoneel.site/work/zonko Date: Mar 2026 - Jul 2026 Summary: Tested and launched Luffy (an AI Slack co-worker), built internal latency logging tools, grew the user base, and rebuilt the company website. [Zonko Labs](https://zonko.ai) is a frontier AI lab building the first truly global, daily-use, everyone-opens-it product born in India. ## What I did - Formed the first layer of internal testing for **Luffy**, an AI co-worker in Slack, helping the dev team catch bugs early. - Built an internal tool to capture **Luffy's data logs**, generating reports on latency and probable slowdowns. - Growing Luffy's user base from **0 to 50+ users**, mainly startups with 1-50 employees. - Stayed in constant contact with early adopters, relaying feedback and feature requests to the dev team, and **rebuilding the company website**, replacing a low-quality, outdated version. ## DevRel Engineer • Keploy URL: https://www.swapnoneel.site/work/keploy Date: May 2024 - Jan 2025 Summary: Built sample applications and a RAG-based chatbot, designed homepage sections, and led developer relations and community programs. [Keploy](https://keploy.io) is an open-source, developer-friendly API testing platform that auto-generates test cases and data mocks from real traffic. ## What I did - Built **multiple new sample applications** and improved existing ones using Rust, Flask, MongoDB, Spring Boot, and Django to demonstrate Keploy’s testing capabilities. - Implemented a fully-functional **RAG-based AI chatbot** for Keploy's documentation page, helping developers navigate the docs faster. - **Designed and developed** new sections on **Keploy's main homepage**, improving the overall layout and developer experience. - Planned and hosted **Keploy Rain of Code**, an open-source contribution programme where I guided 50+ newcomers through making their first contribution to Keploy’s repositories, which also helped Keploy hire fresh talents. - Wrote **25+ technical blogs** covering topics in Software Testing, API mocking, and AI-assisted development. - Managed **social media** handles of Keploy and increased engagement rates by **120%** and follower count by **30%**. ## Software Developer • Wizdom URL: https://www.swapnoneel.site/work/wizdom Date: Jan 2024 - May 2024 Summary: Revamped the app UI and website, optimized codebase performance to reduce load times, and enhanced the podcast audio playback experience. [Wizdom](https://wizdomapp.com) is a platform that provides book summaries and insights via podcasts and readables, helping users learn from the world's best books in minutes. ## What I did - **Revamped the old UI** and built a modern, intuitive interface for their app, significantly improving the overall user experience for over 20,000 users. - **Optimized the codebase** with a focus on performance and scalability, significantly reducing load times by **1200-1800ms** and ensuring a smoother experience across the platform. - **Expanded and rebuilt their website**, creating responsive and high-performance websites that align with their new branding. - **Enhanced the podcast experience**, implementing features that improved audio playback and user engagement with audio content. - **Coordinated with the product and design teams** to successfully transition from legacy systems to a modern tech stack. ## Technical Writer • Tutorials Point URL: https://www.swapnoneel.site/work/tutorials-point Date: May 2023 - May 2025 Summary: Authored a successful Advanced Python course, contributed to database and web courses, and edited over 200 technical articles as a content manager. [Tutorials Point](https://www.tutorialspoint.com) is one of the most popular platforms for learning programming and technology, with millions of learners worldwide. ## What I did - Created an **Advanced Python Course** with over **40 modules** that was purchased by **9,800+ users** over a period of 12 months, making it one of the most successful courses I've shipped. - Contributed to **MySQL** and **Web Development** courses which helped on-board new learners to the platform. - As a content manager, **edited over 200 articles** from 10+ technical writers who worked under me. - Maintained clarity, accuracy, and accessibility in all written content, keeping it approachable for beginners while technically sound for intermediate developers. --- # Projects ## Mesh Hop URL: https://www.swapnoneel.site/projects/mesh-hop Date: 2026-07-19 Summary: Desktop app that automatically discovers, verifies, and routes an isolated browser profile through the best public proxies. A lightweight, self-contained desktop application that automatically discovers working public proxies for a chosen region, verifies them end-to-end, and routes a dedicated browser profile through the best one. It automates the tedious parts of finding, testing, ranking, and rotating public exits, and wires a hardened Firefox profile to the result. ### Tech Stack - **Rust** — Tauri for the lightweight, secure cross-platform desktop framework and sidecar process execution - **Next.js** — for a responsive, modern desktop dashboard and user interface - **Node.js** — for the custom concurrent proxy discovery and benchmarking engine (bundled as a sidecar) - **Tailwind CSS** — for custom theme-driven user interface components - **Firefox** — for launching isolated, proxy-hardened browser profiles with uBlock Origin pre-configured ### Features - Automated **proxy discovery** pulls current candidates (HTTP, HTTPS, SOCKS4, SOCKS5) from multiple public providers. - End-to-end **proxy verification** tests candidates concurrently via HTTPS requests to Cloudflare's trace endpoint to verify country and IP. - Performance **profiling and benchmarking** measures steady-state download speed, speed consistency, and network classification. - One-click **proxied browsing** launches a dedicated, isolated browser profile with reduced WebRTC/DNS leakages and pre-installed extensions. - Dynamic **IP rotation** rotates proxies or refreshes candidates instantly from the desktop interface. ## Blame URL: https://www.swapnoneel.site/projects/blame Date: 2026-07-01 Summary: Client-side Next.js web application designed to scan GitHub repositories, resolve contributor identities, and audit commit counts directly in the browser. A powerful client-side contributor aggregation and outreach tool designed to answer one crucial question for any GitHub repository: "who actually contributed, and how much?" By extracting names, emails, and commit frequencies directly from the browser, Blame enables recruiters, project leads, and open-source audit tools to quickly locate and connect with repository contributors. ### Tech Stack - **Next.js** — powering the client-side single-page web application with Turbopack, React 19, and the App Router - **TypeScript** — ensuring static typing, safety, and robust code structures for component interfaces and utility functions - **GitHub REST API** — fetching commit lists, resolving user profiles, and aggregating histories directly from the browser without intermediate servers - **Tailwind CSS** — styling the user interface with a sleek, modern, dark-themed responsive layout and interactive animations ### Features - Runs client-side to query `api.github.com` without backend storage, keeping scans private - Employs a DP-based fuzzy matcher to filter results with word-boundary and run bonuses - Supports personal access tokens directly from the browser to raise API rate limits - Groups commits by GitHub profile and resolves usernames from private email aliases - Exports aggregated results as a CSV file or copies them directly as markdown - Filters merge commits, sorts results, and limits the total contributor list size ## ANRL URL: https://www.swapnoneel.site/projects/anrl Date: 2026-06-08 Summary: Graph-native representation language explicitly designed to optimize attention allocation and semantic saliency for Large Language Models. A paradigm-shifting representation system designed specifically for Large Language Models, replacing human-centric data formats like JSON with transformer-optimized schemas. By explicitly encoding how a model should "think" about data, ANRL eliminates attention drift and structural noise to provide AI systems with prioritized reasoning, epistemic clarity, and unshakeable relational anchoring. ### Tech Stack - **Rust Compiler** — for a seamless, high-performance pipeline that ingests standard formats into ANRL streams - **MessagePack** — for sub-token, highly dense binary serialization of complex graph structures - **Tree-Sitter** — for robust, syntax-aware code ingestion from multiple programming languages into ANRL schemas - **Fastembed** — for opt-in semantic enrichment and smart auto-sensing of implicit document relationships ### Features - Explicit **saliency weighting** to definitively direct the transformer's attention to critical information - Built-in **epistemic confidence markers** to dynamically encode truthiness and prevent hallucinations - Robust **relational anchoring** to securely link conceptual nodes and completely prevent column slippage - Native **causal logic operators** to explicitly distinguish causation from mere association during reasoning steps - High **token density syntax** specifically designed to minimize structural noise and delimiters for optimal context utilization ## Term Chat URL: https://www.swapnoneel.site/projects/term-chat Date: 2026-05-09 Summary: Terminal-based messaging hub for real-time communication, collaboration, and AI-powered interactions. A terminal-based messaging hub designed for real-time communication, group collaboration, and AI-powered interactions, all within the command-line interface. ### Tech Stack - **Node.js** — for the core CLI application (`termchat-cli`) - **npm** — for the core CLI application (`termchat-cli`) - **Supabase** — for the backend and database management - **Cloudflare R2** — for secure cloud storage and file transfers - **Google Gemini API** — for persistent AI-powered chat assistance - **bcrypt** — for secure CLI-based authentication ### Features - **Real-time messaging** for instant DMs and group chats with live presence and unread counters - **AI integration** powered by Google Gemini with persistent chat history and context management - Seamless **sharing of files and directories** (auto-zipped) via Cloudflare R2 - **Secure auth** with CLI-based registration and login with session persistence - **Social ecosystem** that includes global user search, activity-sorted friend lists, and friend request management ## Folio URL: https://www.swapnoneel.site/projects/folio Date: 2026-05-08 Summary: Premium portfolio engine designed for developers and designers. Folio is a state-of-the-art portfolio generation platform designed for developers, designers, and creatives who want a professional, high-performance web presence without the overhead of manual coding. ### Tech Stack - **TanStack Start** — for a seamless, type-safe development experience and ultra-fast performance - **TypeScript** — powering the core application logic and modern state management - **React** — powering the core application logic and modern state management - **Tailwind CSS** — providing a modern, responsive, and highly customizable UI system - **Supabase** — for robust authentication and real-time database persistence - **Radix UI** — for accessible and high-quality UI primitives - **@dnd-kit** — for smooth and intuitive drag-and-drop section management ### Features - Choose from **premium themes** including Terminal, Vercel, Material You, and Editorial - **Live preview dashboard** to see changes in real-time as you customize your content - **Custom section templates** for Galleries, Stats, Timelines, and Link Cards - **Granular customization** with per-section accent colors and theme overrides - **Built-in image support** for avatar and gallery uploads with seamless Supabase integration ## Scholarian URL: https://www.swapnoneel.site/projects/scholarian Date: 2026-05-07 Summary: High-end research platform designed to transform academic tools into a focused, analytical journey. A high-end, editorial research platform that replaces manual literature reviews with an intelligent pipeline that scours semantic databases and engages in context-aware interrogation. ### Tech Stack - **Next.js** — for a premium, type-safe analytical lens workspace - **TypeScript** — for a premium, type-safe analytical lens workspace - **Google Gemini AI** — powering query enrichment and automated synthesis - **Supabase** — for database persistence and secure authentication - **Zustand** — for global state management and optimistic UI updates - **Tailwind CSS** — for a custom-curated, responsive design system - **Base UI** — for high-quality, accessible UI components - **Radix** — for high-quality, accessible UI components ### Features - **Persistent research history** for every search run and report is persisted via Supabase - **Stateful research pipeline** for real-time tracking from query enrichment to report generation - **Smart mode** that prioritizes the finding of the absolute best papers through repeated searches - **Smart refinement** is built-in for intent analysis to narrow down research topics - **Analytical lens workspace** for a premium responsive dashboard optimized for all devices - **PDF export engine** that generates high-fidelity PDF versions of research reports ## Get Response URL: https://www.swapnoneel.site/projects/get-response Date: 2026-05-06 Summary: Terminal-based AI chatbot and automation tool. A terminal-based AI chatbot and automation tool that turns your command line into an intelligent assistant. ### Tech Stack - **Node.js** — for the core CLI logic - **Google Gemini API** — powering the AI-powered responses - **Stack Exchange API** — powering the specialized research mode for technical querying - **Tesseract.js** — for extracting context from images and documents - **PDF-Parse** — for extracting context from images and documents - **Mermaid.js** — for automatic codebase visualization and diagram generation - **Boxen** — for a polished, interactive, and user-friendly terminal interface - **Chalk** — for a polished, interactive, and user-friendly terminal interface ### Features - Ask questions from **direct text input**, file content, or entire directory context - Enter a **persistent chat mode** for back-and-forth conversational sessions - **Automate terminal commands** by describing tasks in plain English - Generate content and handle technical tasks like writing unit tests or scaffolding applications ## Toile URL: https://www.swapnoneel.site/projects/toile Date: 2026-05-05 Summary: Minimal CRUD application to showcase artistic works in an elegant way. A clean, easy-to-use CRUD application for showcasing artistic works — built with a minimalistic and elegant design philosophy. ### Tech Stack - **React.js** — component-driven UI with smooth interactions - **GSAP** — animations and page transitions - **Tailwind CSS** — utility-first styling for a responsive layout - **Appwrite** — backend for CRUD operations and seamless user authentication ### Features - Create, read, update, and delete artistic entries - Smooth, performant animations powered by GSAP - Secure user authentication via Appwrite - Fully responsive across mobile and desktop ## Omni Learner URL: https://www.swapnoneel.site/projects/omni-learner Date: 2026-05-02 Summary: Universal AI-powered translation platform design for educational accessibility. An award-winning design concept developed for the Smart India Hackathon 2023, aimed at breaking language barriers in education through high-speed, affordable AI translations of global resources. ### Tech Stack - **Figma** — for end-to-end UI design, from low-fidelity wireframes to high-fidelity interactive prototypes ### Features - **256+ language support**, a massive scaling concept designed to make global knowledge truly accessible - **Lightning-fast translation** where the UI workflow is optimized for translating educational resources in seconds using deep learning - Designed with a focus on student accessibility, featuring **low-cost tiers** for individual users - An intuitive **"Explore" functionality** to discover translated resources from language A to language B ## Y Dub URL: https://www.swapnoneel.site/projects/ydub Date: 2026-05-01 Summary: Premium AI-powered video dubbing platform design. A high-fidelity design conceptualized for the Smart India Hackathon 2023, focused on making AI video dubbing accessible, affordable, and accurate for the Indian market. ### Tech Stack - **Figma** — used for end-to-end UI/UX design, from wireframing to interactive prototyping ### Features - **Lip-sync conceptualization** with an intuitive interface for a deep-learning-based lip-syncing engine - **Inclusive language strategy** designed to accommodate multi-script layouts for 32+ Indian languages - **Growth-driven UI** that incorporated high-impact data visualization (10x watch time, 300% revenue) to bridge user trust --- # Blog Posts ## What Is Granular Access Control? RBAC, ABAC, and AI URL: https://www.swapnoneel.site/blog/what-is-granular-access-control Date: 2026-09-09T17:25:41.000Z Summary: What is granular access control? The five dimensions a permission can narrow on, how it differs from RBAC and ABAC, and where AI agents break it. "Can this person use the AI API?" is one question. "Can this service call gpt-5, at most 200 times an hour, with a 50 dollar cap this month, with no file system tools attached, and only until Friday?" is a completely different question. Granular access control is what turns the first question into the second one. It means defining permissions at the smallest useful unit, so an identity can take exactly one action, on exactly one resource, within exactly one budget, under exactly one set of conditions, and nothing else. That idea has a name, the principle of least privilege, and it just means giving each caller the minimum access it needs. Granular access control is how you actually write that down. And the reason it stopped being a nice-to-have this year is that most of the things asking for permission are no longer people. ## Access control is a dial, not a switch Strip a permission down and it is three things: a **subject** (who is asking), an **action** (what they want to do), and a **resource** (what they want to do it to). Every access control system you have ever used is just a way of writing those three things down and checking them. Coarse-grained access control leaves all three parts wide open. "Engineers can use the database" is one permission covering forty people, six verbs, and every table you own. It is one line of config, and it is fast to write, which is exactly why it survives so long. Granular access control narrows the same three parts instead. "The reporting service can run SELECT on the `orders` table, and nothing else." One caller, one verb, one table. Think of a house key against a hotel keycard. The house key opens everything you own, forever, and the only way to take it back is to change the lock. The keycard opens room 402, on floor 4, until Friday at 11 in the morning, and the front desk can kill it from the lobby without touching a single door. Both are access control. The keycard is granular. ![Granular access control illustrated as a hotel keycard](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/hnm88ok73kltdi8mcuu7.png) So granularity is not a product you buy or a model you adopt. It is how far down you are willing to turn the dial, and how much extra work you are willing to do in exchange. Turn it too far and you get a permissions table nobody can read. Leave it too coarse and one leaked credential owns the whole system. ## The five dimensions you can narrow a permission on Most explanations of granular access control stop at "give people less access", which is true and completely useless as advice. The useful version is that a permission can be narrowed along five separate axes, and you can turn each one independently. ![The five dimensions of granular access control](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/1s1helwzy5pt4fwewzsa.png) Here they are, from the one everybody already does to the one almost nobody does. ### 1. Who is asking The identity. This is the axis every system starts with, and the one that changed the most recently. For a long time a subject meant a person, or a service account somebody created once and forgot about. Now a subject is just as likely to be a coding agent, a CI job, a background worker, or a chain of three agents where the last one has no idea which human started the request. These are called non-human identities, and they are the ones quietly holding most of the over-permissioned access. Sonrai's cloud access research, published in May 2026, found that [92% of identities with sensitive permissions](https://sonraisecurity.com/blog/why-92-percent-cloud-permissions-never-used/) did not use them once in 90 days, and 87% of that group were machine identities. That is a vendor scanning its own customers and not an independent audit, so take the exact number lightly. The fix here is easy to say and annoying to do: one identity per caller, never one shared credential per team. ### 2. What action they want to take Read, write, update, delete. List versus fetch. Call a model versus look at what models exist. Coarse systems have two levels here, usually named something like read and admin. Granular systems split up the verbs that can do different amounts of damage. Being able to view a log line and being able to export the whole log table are not the same permission, even though both are "reading". ### 3. Which resource, exactly This is where granularity gets real, because a resource is rarely one flat thing. A database can be scoped at the server, the database, the table, the column, or the row. An AI setup can be scoped at the provider, the model, or a single tool that a model is allowed to invoke. Each step down that ladder means less damage if the key leaks, and one more thing to maintain. Row-level scoping is the one worth knowing by name. It answers "which of these records can you see", which is a different question from "can you see this table", and it is the difference between a support agent seeing their own tickets and a support agent seeing everybody's. ### 4. How much they can consume Here is the axis classic access control mostly ignores, and it is the one that bites hardest with AI. Permission has traditionally been a boolean. You can call this endpoint, or you cannot. But when a single call can cost real money and a loop can make ten thousand of them, "yes" without a ceiling is not a permission, it is an open tab. So the granular version attaches quantity to the grant. This key may spend 200 dollars a month. This key may burn 10,000 tokens an hour. This key may make 100 requests a minute. Developers have been asking providers for exactly this for years, and OpenAI's own developer forum carries a [feature request for per-key spending limits](https://community.openai.com/t/api-key-specific-rate-and-spending-limits-would-be-good/1374358) with people piling into it. ### 5. Under what conditions The context around the request. Time of day, source IP, environment, device posture, whether the key has expired yet. Expiry is the underrated one. A permission with no end date is a permission you will forget you granted, and every "how did this old key still work" incident starts there. ## Is granular access control the same as RBAC? No, and this trips up a lot of people, so it is worth separating properly. ![RBAC, ABAC, and ReBAC compared](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/lhlbqhdkv87js27zudwx.png) **RBAC** (role-based access control) groups permissions into roles and hands roles to identities. It is the most common model in the world because it maps onto how companies actually think. You are a developer, developers get the developer role, done. RBAC can be granular or coarse depending entirely on how you write the roles. That is the part people miss. A role called `admin` with every permission attached is RBAC and it is not granular at all. The trouble shows up when you try to push RBAC down the dial. Every new condition needs its own role, so you get `developer-staging`, `developer-staging-eu`, `developer-staging-eu-readonly`, and pretty soon nobody can tell you what any of them do. The industry name for this is [role explosion](https://www.osohq.com/learn/rbac-vs-abac), and it is the standard failure mode of a team that discovered granularity and only had roles to express it with. **ABAC** (attribute-based access control) fixes that by evaluating attributes at request time instead of pre-baking roles. Department, clearance, resource classification, time, location. It gets you much finer control without the role count exploding, and the price you pay is that when a request gets denied, you have to trace which attribute failed instead of just reading a role name. **ReBAC** (relationship-based access control) asks a third question: how is this subject related to this resource? You can edit the doc because you created it. You can see the profile because you manage that person. Google Drive works this way, and so does every app where ownership is the real rule. Well, which one should you use? Honestly, most real systems end up with RBAC for the broad strokes and one of the other two layered on for the cases roles cannot express. Granularity comes from the rules you write, and not from the model you picked. You can write a terrible coarse ABAC policy. Plenty of people have. ## Why AI made this urgent I run Claude Code and GPT Codex daily, and both of them do things a normal API consumer never did. They read files and call tools. And they chain several model calls together off one instruction I typed half-awake. That breaks the two assumptions most permission systems were built on: that whoever is asking is a person, and that every request is something a person actually asked for. ![AI agents expand access control risk](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/b4iz8a40bn41yzz76ga2.png) The numbers back this up. 1Password surveyed 1,000 security and engineering staff at large US firms in late May and early June 2026, and found that agents in production reached [roughly twice as much data](https://www.helpnetsecurity.com/2026/07/29/1password-ai-agent-governance/) as had actually been approved. In the same survey, 33% of developers running agents reported a breach or security incident tied to an over-privileged non-human identity, and 40% said they leave agents holding persistent access to systems and secrets after the task is finished. None of that is an AI-specific vulnerability. It is the same over-permissioning problem as always, running at a speed and volume that humans never generated. ## Where granular access control breaks in practice Everybody agrees with least privilege. Almost nobody has it. So it is worth being specific about where the plan falls apart, because the reason is usually the setup, and not laziness. **A provider key cannot be narrowed.** Your OpenAI or Anthropic key is a single credential with your whole account behind it. There is no version of it that means "gpt-4o only, 50 dollars, no tool calls". So the moment more than one service needs model access, you either share one key and lose all attribution, or you mint several and lose all central control. **Enforcement lives in the wrong place.** If the permission check is inside your application code, then every new service, notebook, cron job, and intern's side script has to re-implement it correctly. One of them will not. And the ones that skip it will not show up in any policy list, because they never registered with the policy system in the first place. **You cannot enforce what you cannot attribute.** At Zonko Labs I built an internal tool that captured our AI product's data logs and generated reports on latency and probable slowdowns, and the thing that made it useful was that every log line could be traced back to a specific caller. Without that, a spend spike is just a number going up. You cannot tighten a permission when you do not know which caller needs loosening. ![Common granular access control failure modes](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/sca2l1cfdi41lpzjhyar.png) So the pattern behind all three is the same. Granular access control needs a place in the request path that sees every call, knows who made it, and can say no before the call leaves your network. ## Where the gateway comes in Let me be honest about where this actually helps first. If you are one person with one API key on one machine, you do not need any of this, and a gateway will not make your side project safer. It also cannot fix a permission model you have not thought about. A gateway enforces the rules you write, so if you write "allow everything", you get everything, faster. But the moment there are several services, several models, and a few agents in the mix, the check has to sit in the traffic path. That is what an AI gateway is: a proxy that every model call goes through. Which makes it the one place you can check a permission once, instead of in every service. ![AI gateway enforcing permissions on model calls](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/0bpbcye3j3npblkge18e.png) [Bifrost](https://www.getmaxim.ai/bifrost) is the open-source one I keep pointing people at, partly because the whole thing sits in [a public GitHub repo](https://github.com/maximhq/bifrost), so you can read exactly what it enforces rather than trusting a feature list. The thing you hand out is a **virtual key**. Your real provider credentials sit inside the gateway, and each caller gets its own scoped key instead. And the things you can scope that key on are almost exactly the five dimensions above. Take the rule in plain words first: > This key belongs to the support team. It can only use OpenAI, only the `gpt-4o` model, and it stops working on the first of next year. Written out in Bifrost's [governance config](https://docs.getbifrost.ai/features/governance), that is: ```json { "id": "vk-support-bot", "is_active": true, "allow_all_providers": false, "expires_at": "2027-01-01T00:00:00Z", "provider_configs": [ { "provider": "openai", "allowed_models": ["gpt-4o"], "blacklisted_models": [] } ], "team_id": "team-support" } ``` Reading that back: `allow_all_providers` set to false means any provider not listed here is denied, including ones added to the gateway later. `allowed_models` narrows dimension three to a single model. `expires_at` is dimension five, so the key dies on its own without anybody remembering to revoke it. And `team_id` attaches it to a team, which is where the budget lives. A few more things worth knowing: **Spending and rate caps sit on the key itself.** A budget is `max_limit` plus a `reset_duration` of `1m`, `1h`, `1d`, `1w`, `1M`, `1Q` or `1Y`, and rate limits are separate `token_max_limit` and `request_max_limit` counters. Blowing through them returns real status codes rather than a generic failure: 402 for `budget_exceeded`, 429 for `token_limited`, 403 for `model_blocked`. That distinction sounds small until you are the one reading the error at 2 in the morning. **Tool access is deny-by-default.** If you have wired up MCP servers, a virtual key with no MCP configuration gets no tools at all. Where tools are granted, they are listed explicitly in `tools_to_execute`, and the key's list acts as a ceiling that a request cannot widen. That is dimension three pushed all the way down to individual tools, which matters a lot given how much damage one file system tool can do inside an agent loop! (If MCP is new to you, I wrote [a beginner's guide to MCP servers](https://www.swapnoneel.site/blog/what-is-an-mcp-server) first.) **Seeing the dashboard is a separate grant from calling a model.** Bifrost ships three system roles, Admin, Developer and Viewer, carrying 42, 27 and 14 permissions, and you can build custom ones by toggling resource and operation pairs. On top of that sits data access control, which decides which rows a user sees at all: `own-data`, `team-data`, or `all-data`. Being allowed to open the logs page and being allowed to see everybody's logs are two different grants. **Write the policy once, not once per person.** [Access profiles](https://docs.getbifrost.ai/enterprise/access-profiles) let you define one policy and auto-issue a per-user virtual key from it, each with its own budget counter. Edit the template, propagate, and every key follows. Keys can rotate on a schedule from `1h` up to `365d`. This is the part that decides whether any of it survives the team getting bigger. If every new engineer needs a hand-written policy, somebody will quietly hand out a shared key by month three. **Audit logs are signed.** Administrative events can be HMAC-signed and exported as JSON, JSON Lines, or syslog in the RFC 5424 format that SIEM tools ingest. Knowing who did something and being able to prove it later are the same job, and homegrown setups usually only manage the first half. Bifrost publishes its own benchmarks (roughly 20 microseconds of added latency at 5,000 requests per second), and those are vendor benchmarks run on a vendor harness, so measure your own. The point holds whichever gateway you use: the check belongs on the path, and not copy-pasted into fifteen codebases. If governance at this layer is the actual problem you are solving, I also went through [the tools in this space](https://www.swapnoneel.site/blog/top-5-ai-agent-governance-platforms) in more detail. ## Where should you set the dial? The floor is not RBAC, and it is not ABAC either. The floor is being able to answer one question about your own system right now: **If a credential leaked this minute, what exactly could someone do with it, and how much could they spend before anyone noticed?** If the honest answer is "everything, and I have no idea", the fix is not a bigger access control model. It is a first cut at all five dimensions, in this order: one identity per caller, then a spending cap on each one, then an expiry date, then a model or resource allowlist, then conditions. That order is deliberate, because knowing who called and capping what they spend buy you the most safety for the least work, and conditions buy you the least. ![Recommended order for setting granular access controls](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/61eygqjuow0cu6nh8hzp.png) You will not get to per-row, per-tool, per-hour granularity, and you should not try. Nobody is running the theoretically correct permission model. The teams doing well are the ones who pushed the dial two notches past a shared key and then actually maintained it. Anyway, I am curious where other people drew this line. If you have a permission setup that survived a team getting bigger, or one that collapsed into a shared key by month three, tell me in the comments, I want to hear the failure stories more than the success ones. You can find me on [X](https://x.com/swapnoneel123), or read the rest of my writing on [my site](https://www.swapnoneel.site). ## Three Insights for Building Great Claude Skills URL: https://www.swapnoneel.site/blog/building-great-claude-skills Date: 2026-09-08T18:32:06.000Z Summary: The three things that separate Claude skills that work from ones that never trigger: a clear description, a lean SKILL.md, and putting fragile steps into scripts. Most Claude skills that never fire are not broken. Their description is. That one fact is behind almost every "why won't my skill trigger" post you'll find. Three things separate a working skill from one that sits there ignored: a description written like a routing rule, a `SKILL.md` kept short on purpose, and pushing fragile steps into scripts instead of long instructions. Get those three right, and everything else gets much easier. ## First, what a Claude skill actually is A skill is a folder with a `SKILL.md` file in it. At the top of that file, you have a small YAML header with a name and a description, followed by instructions written in markdown. That's the whole concept. Anthropic launched skills in October 2025 and [made the format an open standard](https://venturebeat.com/ai/anthropic-launches-enterprise-agent-skills-and-opens-the-standard) in December, so Claude Code, the API, and claude.ai all use the exact same folder structure. The interesting part is how skills load. Claude doesn't read your entire skill upfront. When it starts up, it reads only the name and description of every installed skill, costing around 100 tokens each. Claude loads the main instructions only when it decides your skill fits what you asked for. And it reads extra helper files only when those instructions specifically point to them. Anthropic calls this [progressive disclosure](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview). Two of the three insights below come down to working with this design instead of fighting it. ![A folder reveals a skill’s metadata, instructions, and helper script loading in stages.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/qpfssqij5pizxiple4ef.png) This means skills are cheap to keep around, costing you tokens only when they actually run. That single design choice shapes what makes a skill good or bad. ## Insight 1: Your description is a routing rule, not a summary Here is where people get confused. Your description isn't a friendly summary for humans browsing your repository. It is a routing rule Claude uses to pick your skill out of the pile. ![Prompt paths enter a routing switch; precise triggers pass and vague descriptions stop.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/0qb993dvkxs1ylwkf0oz.png) Remember what Claude actually sees at startup: only the name and description of each skill. Nothing else. When you send a prompt, Claude decides whether to open your skill's full text based purely on that description. If the description is vague, Claude never reads the instructions. The best instructions in the world won't help if your skill never triggers. That's why a skill that won't trigger is almost never a code bug. It just means your description doesn't explain when to trigger using the words you actually type. A good description handles two jobs: what the skill does, and when Claude should run it. Write it in the third person (like "Reviews pull requests" instead of "I review" or "Review this"). Because Claude reads this inside its system prompt, mixing perspectives can confuse the model. You have up to 1,024 characters, so use that space for the exact phrases you would normally type. ```yaml --- name: reviewing-pull-requests description: Reviews pull request diffs against the team's checklist and flags risky changes. Use when the user asks to review a PR, review a diff, or check staged changes before merging. --- ``` Notice that the description names the triggers out loud: "review a PR", "review a diff", "check staged changes". Compare that to "Helps with code review", which competes with everything Claude already knows about code review and loses. The name matters less than the description, but it has strict rules that catch people off guard. It must use only lowercase letters, numbers, and hyphens, capped at 64 characters. It cannot include the words "anthropic" or "claude". Anthropic recommends names ending in -ing (`reviewing-pull-requests`, `analyzing-spreadsheets`) to clearly show what action the skill takes. A name like `claude-pr-helper` breaks two rules at once, while a generic name like `helper` gives no useful information. There is real data behind this. One developer ran [650 activation trials](https://medium.com/@ivan.seleznov1/why-claude-code-skills-dont-activate-and-how-to-fix-it-86f679409af1) on the same skills. They found that passive descriptions triggered far less reliably than direct instructions starting with "Use this skill when." While this isn't an official benchmark from Anthropic, the takeaway matches both the official documentation and real-world use: name the specific situation where Claude should trigger, not just what it can do. Also, keep your scope realistic. If it's too broad ("helps with frontend"), Claude will pick its own built-in knowledge instead. If it's too narrow ("fixes React 18 hydration errors in Next.js 14 app router"), it won't run if you phrase your prompt slightly differently. You are describing a general task, not a specific bug ticket. ## Insight 2: Progressive disclosure is a token budget you spend Once a skill triggers, its `SKILL.md` body loads into the same context window as your conversation, your files, and every other skill's metadata. Anthropic makes a great point in their [authoring guide](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices): the context window is shared space. Every token you waste in your instruction file is memory the model can't use for your actual work. This means your main skill file should be smaller than you might expect. The official recommendation is to keep `SKILL.md` under 500 lines. Move anything longer into separate files that load only when Claude needs them. Loading happens in stages, and each stage has a different cost: | Level | When it loads | Rough cost | | ------------------------- | ----------------------- | --------------------- | | Name and description | Always, at startup | ~100 tokens per skill | | SKILL.md body | When the skill triggers | Under 5k tokens | | Bundled files and scripts | Only when referenced | Nothing until read | ![Instruction pages narrow through a funnel so only the needed context reaches the working window.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/cdsfsn8vb8rqw6avn3d7.png) That third row is the real secret. You can include a 2,000-line API guide, datasets, or code examples, and they cost zero tokens until Claude actually needs them. So the best approach is a lean `SKILL.md` that acts like a table of contents, pointing Claude to larger files when necessary: ```plaintext reviewing-pull-requests/ SKILL.md # short: the workflow and when to read what checklist.md # loaded only when reviewing examples.md # loaded only when Claude wants a sample scripts/ diff_stats.py # executed, never loaded into context ``` The documentation highlights two practical rules here. First, keep linked files only one level deep and link them directly inside `SKILL.md`. If you bury files inside subfolders, Claude often previews just the first 100 lines and misses the rest. Second, don't explain concepts Claude already understands. Anthropic uses a straightforward example: a good instruction to extract PDF text is about 50 tokens of code. A bad one uses 150 tokens because it starts by explaining what a PDF is. Claude already knows what a PDF is! Notice what I'm doing here, too. I'm not explaining what a token is or how context windows work. If you're building skills, you already understand those basics. That's the exact same mindset your skills need: only write what the model doesn't already know. I care about this point a lot because of my work building [ANRL](https://anrl-site.vercel.app). It's a compact data format created to cut token waste and context fragmentation, and it reduced formatting overhead by more than 40%. When you spend weeks fighting to save 40% on tokens, watching someone waste 3,000 tokens in `SKILL.md` re-explaining JSON is painful. Keeping your skills lean isn't just about clean formatting—it protects the model's working memory. ## Insight 3: Let code do the deterministic work The last one separates skills that feel reliable from skills that mostly work. If a step is deterministic (parse this file, validate this schema, sort these rows), do not write instructions asking Claude to be careful. Write a script and tell Claude to run it. When Claude runs a script through bash, only the output comes back into context, not the code. So a bundled `validate.py` is cheaper than asking Claude to generate the same validation every time, and it does the exact same thing on every run instead of a slightly different thing. ![A plan crosses a guarded script bridge, passes validation, and reaches a stable output.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/sescauz6xlvbfj8wgbv1.png) Anthropic uses a great phrase for writing these helper scripts: "solve, don't defer." If a file might be missing, handle that check right inside the script. Don't let the script crash and force Claude to guess a fix, because improvised fixes break reliability. The same rule applies to unexplained settings: setting a 30-second timeout with an explanatory comment is much better than leaving a random number like `47` that no one can explain later. The bigger idea is giving Claude freedom only when the task allows it. Anthropic's docs share a helpful comparison: Some tasks are like a narrow bridge with steep drops on both sides, like running database migrations in a strict order. For those, give Claude a single script and tell it not to alter the command. Other tasks are like an open field with no dangers, like reviewing code where the best feedback depends on what Claude finds. For those, give general guidelines and trust the model to choose the right path. Making mistakes here hurts in both directions: adding too many rigid rules in an open field makes the skill inflexible, while giving too little direction on a narrow bridge leads to errors. For bulk edits or risky actions, the documentation suggests a three-step pattern: plan, validate, and execute. Have Claude write its planned changes to a file first. Next, run a script that checks that plan for mistakes. Finally, apply the changes only if the check passes. It might sound like extra work, but it's the difference between catching an error in a safe file and catching it after it breaks your database. ## When one skill becomes twenty Those three insights help you build a solid individual skill. But developers rarely stop at just one. Once your team starts collecting dozens of skills, two quiet problems start showing up. First, skills can connect to external tools through MCP (if you haven't used MCP yet, check out my [beginner's guide to MCP servers](https://www.swapnoneel.site/blog/what-is-an-mcp-server)). The easiest way to think about the difference: MCP provides the tool, while the skill teaches Claude how to use it for your specific workflow. But pay close attention to Anthropic's security warning: a skill can tell Claude to run tools in ways the author never mentioned. That's why they recommend checking every file inside any skill you didn't write yourself. Second, it's best practice to test skills across different models like Haiku, Sonnet, and Opus. A skill is only as reliable as the model running it. In a team environment, that means your skills end up calling different models across different API providers. Combine that with several teammates installing community skills from GitHub, and you aren't just managing simple markdown files anymore. You have unmonitored tool calls and model requests running across your team. That's an entirely different problem. ![A shelf of skills expands into a connected ecosystem inspected for hidden tool risk.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/8hu0md48n2dqt5acghor.png) ## Do you need a gateway for your skills? If you're running just two skills on your laptop, you don't. Seriously, skip this section and go write your skills instead of setting up infrastructure. A gateway won't make an individual skill better, and it won't fix a bad description. A gateway starts to matter when you can no longer answer simple questions: Who is allowed to run which tool? Which skill triggered this request? Whose API budget is paying for it? When you hit that point, routing all that traffic through one place makes life much easier. That place is an AI gateway, and the one I keep returning to is [Bifrost (by Maxim AI)](https://www.getmaxim.ai/bifrost), an open-source AI gateway and control plane for LLMs, MCP tools, and agents ([repo here](https://github.com/maximhq/bifrost)). ![A small setup runs directly while a growing team routes tools and models through one gateway.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/1d5dafilojaeqhim8tnp.png) Two of its core features line up directly with the scaling problems above. To secure tool calls, Bifrost acts as an [MCP gateway with deny-by-default tool filtering](https://docs.getbifrost.ai/mcp/filtering). Without explicit permissions, zero tools are accessible. You define what to unlock across client settings, request headers, and virtual key configurations. This means a skill could ask for a filesystem MCP server, but its key can restrict execution strictly to `read_file`, completely blocking destructive actions like `delete_file`. You don't have to depend on plain text instructions in a prompt to keep your systems safe. (It even has an MCP Code Mode that runs tool workflows inside a sandbox instead of dumping every tool schema into your context, cutting token usage by over 92% across large tool sets.) To manage model access, Bifrost provides [virtual keys and governance rules](https://docs.getbifrost.ai/features/governance). Each key gets its own list of approved models (across 25+ providers and 10,000+ models), an automatic budget cap with reset periods, and custom rate limits. So when you want your team to test a skill across Haiku, Sonnet, and Opus, you give them one key with an allow-list and a spending cap instead of juggling five API keys in a spreadsheet. Provider fallback and retries are built in, so a skill doesn't fall over if one provider has an outage. Performance is where this actually holds up. If a gateway is slow, developers route around it. Bifrost is written in Go with memory pooling and pre-warmed connections, keeping added gateway overhead to about 11 microseconds at 5,000 requests per second on a single t3.xlarge instance. In Maxim's published benchmarks against LiteLLM (tested with 500 concurrent users on AWS), Bifrost delivered 9.5x higher throughput (424 req/s vs 44.84 req/s) and roughly 50x lower P50 latency (804 ms vs 38.65 s). Those are vendor benchmarks, so treat them as such, but the main point holds: the gateway manages your traffic without becoming the slow part. None of this is about writing an individual skill. It's just what helps once your skills work well and you start scaling up. ## So what actually makes a Claude skill great? It's not about making a giant `SKILL.md` file. It's much simpler than that. Write your description like a clear routing rule in the third person, using the exact words you'd naturally type in a prompt. Keep the main instructions short and let extra reference files handle the heavy lifting, because context memory isn't free. And move fragile, repetitive tasks into code scripts so they run consistently every single time instead of guessing. Those three habits make the difference between a skill that runs reliably and one that sits forgotten in a folder. ![Three rising habits—routing, lean instructions, and deterministic code—build a reliable skill.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/noxgvap6bfhk954kxr02.png) Using an AI gateway matters as well, but only later on. Don't worry about it until you have more skills and tools than you can easily keep track of in your head. You'll know when you cross that line. Until then, setting up a gateway is just extra work you don't need. If you've built skills that trigger reliably, I'd love to hear how you wrote your descriptions to make them work. That's still the area where most people spend the least effort. Come share your thoughts on [X](https://x.com/swapnoneel123), or check out my other posts at [my site](https://www.swapnoneel.site). ## How to Cut Time to First Token (TTFT) in LLM Apps URL: https://www.swapnoneel.site/blog/reduce-time-to-first-token Date: 2026-09-04T15:49:58.000Z Summary: Time to first token is the blank-screen wait before your LLM replies. Here is how to cut TTFT, from prompt caching to routing, biggest wins first. Whenever you type something into an AI app like ChatGPT or Gemini, and hit enter, you have to stare at nothing for half a second, one second, and sometimes even more. Then the words start pouring out fast. The wait time that you always experience in between these two events is called time to first token, or TTFT, and it is the single thing your users actually feel. Time to first token (TTFT) is the gap between sending a request and the first token coming back. To cut it, shorten and cache your prompt so the model reads less, stream the reply, keep connections warm, and route around slow providers. The biggest lever is prompt length, since most of that wait is the model reading your input. Now let me show you where the time actually goes, and how we can fix this. So, let's begin! ## What Time to First Token Actually Is When you send a prompt, the model does two very different jobs. First it reads everything you sent. Every token of your system prompt, your chat history, your retrieved documents, your question. It runs all of that through the network in one big forward pass and builds an internal memory of it. This step is called prefill, and nothing comes back to you while it runs. Then it starts writing, one token at a time. That second phase is where you see text stream in. TTFT covers the first phase plus everything around it: the network trip to the provider, any time your request spends waiting in a queue, and the prefill itself. Here is the part that matters. On any prompt that is not tiny, prefill dominates, and the more tokens you send, the more [prefill work](https://clickhouse.com/resources/engineering/llm-inference-latency) the model does before it can start. A short prompt starts answering quickly. A prompt stuffed with 8,000 tokens of context makes the reader sit and watch the cursor. ![Illustration of the path from a prompt to the first token](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/2a7hdpvlwqgicugxqn6t.png) ## What Is a Good Time to First Token? It depends on what the app is for. The [Modular LLM Inference Handbook](https://handbook.modular.com/llm-inference-basics/llm-inference-metrics) puts it simply: a chatbot usually needs TTFT under 500 milliseconds to feel responsive, and a code-completion tool needs it below 100 milliseconds or it feels broken. A batch job that emails a summary once a day can take five seconds and nobody cares. So before you optimize anything, decide which of these you are. A chat UI and a background report have completely different budgets, and chasing 100ms on a nightly job is wasted effort. ![Illustration comparing TTFT targets for chat, code completion, and batch work](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/y9jvmyx3gxotsz4y6atd.png) ## The Fixes, Biggest Wins First Here is the order I actually work through. The early steps cost you almost nothing and pay back the most. The later ones matter, but only after the cheap wins are done. Do not skip to the bottom. ![Illustration of TTFT improvements ranked from measurement to routing](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/rf535slag36i104uuh4q.png) ### 1. Measure TTFT on its own, before you touch anything You cannot cut a number you are not looking at. And most teams are not looking at this one, because their logging records total response time and stops there. Total time hides TTFT completely, since a slow first token and a slow-typing model both show up as one big number. So split them. Log the time from request-sent to first-token-received as its own field, separate from the time to finish. Do it at the p50 and the p99, because the average will lie to you and the tail is where users rage-quit. This is not theory for me. At Zonko Labs I built the internal tool that captured the data logs for Luffy, an AI co-worker that lived in Slack, and turned them into reports on latency and probable slowdowns. The first thing that tool taught everyone was that our "the bot is slow" complaints were almost always a first-token problem, not a typing-speed problem. You only learn that if you measure the two apart. If your traffic runs through an AI gateway, you can track this without building custom timers in your app. For example, [Bifrost](https://www.getmaxim.ai/bifrost) exports a metric called [`bifrost_stream_first_token_latency_seconds`](https://docs.getbifrost.ai/features/observability/prometheus). It measures TTFT automatically across OpenAI, Anthropic, or any other provider, so you get clean p50 and p99 charts right out of the box. ### 2. Shorten the prompt This is the biggest lever you fully control, and it is the one no tool can pull for you. Prefill scales with input length, so every token you delete is prefill work the model never has to do. Look hard at what you are actually sending. Most apps ship a system prompt that grew over months and never got trimmed, a full chat history when the last four turns would do, and RAG retrieval that dumps ten documents when three answer the question. Cut the system prompt to what the model needs, not what makes you feel safe. Truncate or summarize old history instead of resending all of it. Retrieve fewer, better chunks. None of this is glamorous, and all of it moves TTFT more than the fancy stuff. Be honest about the tradeoff, though. Cutting context can cost you answer quality, so trim, then check your evals, then trim again. This is the one place where going too far actually hurts, so it earns the care. ### 3. Cache the stable part of your prompt Most of what you send is the same every single call. The system prompt does not change. The tool definitions do not change. Only the user's question at the end changes. Prefill does not know that, so it re-reads the whole thing every time, unless you turn on caching. There are two flavors, and they are not the same thing. Prompt caching, also called prefix caching, is offered by the model providers themselves. It stores the processed form of a stable prefix so the next call reuses that work instead of redoing it, which is what [prefix caching](https://llm-d.ai/blog/kvcache-wins-you-can-see) buys you. Put the parts that never change at the very front of your prompt and the changing question at the end, so the cache has the longest possible prefix to hit. Semantic caching is the bigger swing. Instead of caching the prompt's prefill, it caches the whole answer, and serves it when a new question means the same thing as an old one. On a hit, you skip the model entirely, so TTFT collapses from "prefill a big prompt" to "look up a vector," which is milliseconds. I wrote a [full explainer on semantic caching](https://www.swapnoneel.site/blog/what-is-semantic-caching) if you want the mechanics. Let me make the caching point concrete with a small test you can predict. Call A sends a 3,000-token system prompt that never changes, then a 40-token question at the end. Call B folds the same information into a fresh 3,000-token prompt that gets reworded on every request, then the same 40-token question. Both send roughly the same number of tokens. On the hundredth request, which one has the lower TTFT? Call A wins, and it is not close. Call A's 3,000-token prefix is identical each time, so after the first call it lives in the prompt cache and its prefill is skipped. Call B looks different to the cache every time, so it pays the full prefill on all one hundred calls. Same token count, wildly different first-token time, decided entirely by whether the front of your prompt holds still. That is the whole game with caching: keep the front stable. ### 4. Stream the response, and know its one limit Streaming sends each token to the user the instant it is generated, instead of waiting for the full answer. Turn it on. Every serious chat UI streams, and it is a one-line change in most SDKs. Now the honest part, because it trips people up. **Does streaming reduce TTFT?** No. Streaming does not make the first token arrive any sooner, since prefill still has to finish first. What it changes is everything after that first token, and it changes how fast the whole thing _feels_. People read a streaming reply as faster than a non-streaming one even when the total time is identical, which is why [faster first tokens](https://codeant.ai/blogs/ai-first-token-latency) matter more than total speed. So streaming is a perception win, not a TTFT win, and the two stack. Streaming only pays off if TTFT is already short, because a three-second blank screen followed by a burst still reads as three seconds of nothing. Fix TTFT first, then stream to make the rest feel quick. ### 5. Keep the connection warm and close Some of your TTFT is pure plumbing, and it hides where nobody looks. Every fresh HTTPS connection pays for a TLS handshake before a single byte of your prompt moves. If your app opens a new connection per request, you are paying that tax every time. Reuse connections with keep-alive and connection pooling so the handshake happens once, not on every call. Distance costs you too. A request from a server in Mumbai to a model endpoint in Virginia spends real milliseconds just crossing the planet and back, twice, before prefill even starts. Pick a provider region near your users where you can, and cut the number of hops between your app and the model. If you are on a serverless setup, watch for cold starts, since a function spinning up from zero can add more delay than the model does. ### 6. Route around slow and rate-limited providers Here is the failure mode that ruins TTFT and has nothing to do with your prompt. The provider is having a bad minute. When a provider is overloaded, your request sits in its queue, and queue time is part of TTFT. When you hit a rate limit, you get a 429 and your client backs off and retries, which stacks seconds onto the first token before it ever appears. Neither of these is fixed by shortening your prompt, because the model never even started. The fix is to not be stuck with one provider. Send the request to the fastest healthy option, and when one starts failing or crawling, fall over to another automatically instead of retrying into the same wall. This is [adaptive load balancing](https://www.swapnoneel.site/blog/what-is-adaptive-load-balancing) applied to model traffic, and it is the difference between one bad provider-minute taking down your p99 and your users never noticing. ## Where a Gateway Does This For You Let me be honest about what a gateway cannot do first. A gateway cannot shorten your prompt. It cannot make a provider's GPU run faster. If your app only calls one model and your prompt is already lean, you do not need a gateway for TTFT. You will get much bigger gains just by trimming your system prompt. Where an AI gateway helps is when you want to handle caching, retries, and monitoring across multiple services without rebuilding them in every backend repo. The gateway sits between your application and your model providers as a reverse proxy, so you wire those fixes once in your infrastructure instead of in your app code. ![Illustration of an AI gateway coordinating caching, metrics, and fallbacks](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/frcs5m2rndvoa1kcu30p.png) I have used [Bifrost](https://www.getmaxim.ai/bifrost), an open-source AI gateway built in Go, and it handles these levers in one place: First, it gives you **TTFT metrics out of the box**. Measuring first-token latency manually usually means writing custom streaming wrappers in Python or Node.js to timestamp the first chunk. Bifrost measures `bifrost_stream_first_token_latency_seconds` at the proxy layer for every request, giving you clean p50 and p99 charts across all your providers without extra client telemetry. Second, it provides [semantic caching](https://docs.getbifrost.ai/features/semantic-caching). If a user asks a question that matches a previous one, Bifrost serves the answer straight from cache in about 10 to 15 milliseconds. That turns an 800-millisecond prefill wait into almost nothing. Plus, cache writes happen asynchronously in the background, so cache misses do not add any latency to your stream. Third, it runs **automatic fallbacks**. When a provider is overloaded or returns a 429 rate limit, your request sits in a queue and your TTFT spikes. With Bifrost, you can set fallback rules: if your primary model hangs or errors, it instantly shifts the request to a healthy backup provider instead of leaving your user staring at an empty screen. Finally, there is the question of **proxy overhead**. Adding a gateway puts an extra hop in your network path. If that hop adds 30 or 50 milliseconds, it defeats the entire purpose of optimizing latency. Bifrost is written in Go and adds around 20 microseconds of overhead under load, meaning the hop itself is practically invisible. You still have to fix your prompts first. But once your prompts are clean, putting a gateway in front of your models lets you handle caching, fallbacks, and latency tracking through simple configuration instead of messy glue code. ## Start Here If you do nothing else from this whole post, do three things, in this order. Measure TTFT as its own metric, so you stop flying blind. Shorten and cache your prompt, because that is where the biggest, cheapest wins live. Then stream, so the rest of the answer feels fast. ![Illustration of the three steps to reduce TTFT](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ffbxbe31nm566wdsjr83.png) The truth most infrastructure posts bury is that for an app that calls an API, time to first token is mostly about prompt length and caching, not about exotic GPU tricks you will never touch. The GPU tricks are real, but they belong to the people running the models. Your levers are the prompt you send, the cache in front of it, and the route it takes. Those you own completely. Start by logging your p50 and p99 first-token time this week. I would bet the number surprises you, and I would bet a fat system prompt is the reason. If you have a TTFT horror story, or a fix that worked that I left out, tell me. I am [@swapnoneel123](https://x.com/swapnoneel123) on X, and I write more of these on [my site](https://www.swapnoneel.site). ## What Is an MCP Registry? Discovering & Governing MCP Servers URL: https://www.swapnoneel.site/blog/what-is-an-mcp-registry Date: 2026-09-03T17:01:44Z Summary: What is an MCP registry? It helps you discover MCP servers, but not govern them. How discovery works, why it isn't security, and where a gateway fits. Most people consider an MCP registry as "npm for MCP servers." It is a nice line to explain the concept, but it is partially true and extremely half-baked. An MCP registry is a searchable catalog of MCP servers. It stores metadata, like a server's name, where to install it, and how a client should connect, so an AI agent or a developer can find servers to plug in. But here is the part the npm comparison hides: a registry is a discovery layer, not a trust layer. It tells you a server exists. It does not tell you the server is safe, or that your agents should be allowed to run it. That gap between "I found it" and "I trust it" is where teams get burned. Discovering MCP servers and governing MCP servers are two different jobs, and a registry only does the first one. So let's get both straight. > If you want to know more about what an MCP server is and how it works, I wrote a [beginner's guide to MCP servers](https://www.swapnoneel.site/blog/what-is-an-mcp-server) first. This post picks up where that one left off. ## What an MCP Registry Actually Is An MCP registry is a metadata catalog. It does not hold the server's code. It holds a small record that describes the server and points to wherever the code actually lives. ![A searchable MCP registry catalog with server metadata cards.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/50ddyc6s28ybregird9b.png) This trips people up, so let me separate the two clearly. Your code lives on a package registry: npm, PyPI, Docker Hub. That is where the actual bits get downloaded from. The MCP registry sits one level up and just stores a pointer. For example, a `weather-mcp` package can live on npm, and the MCP registry entry maps "weather v1.2.0" to `npm:weather-mcp`. The registry is the index card. npm is the shelf. Each entry follows a standard shape called `server.json`. It records the server's unique name, where to find it (an npm package name, or a remote server URL), how to run it (command-line args, environment variables), and some discovery data like a description and what the server can do. That is it. A registry is a stack of these `server.json` cards, plus an API to search them. And that is genuinely useful. Before any of this, finding an MCP server meant scrolling GitHub READMEs and random Discord links. A registry turns that into one queryable list. But notice what a stack of index cards can and cannot do for you, because that is where the trouble starts. ## The Official MCP Registry The one most people mean by "the MCP registry" is the [official MCP Registry](https://registry.modelcontextprotocol.io), which launched in preview on [8 September 2025](https://blog.modelcontextprotocol.io/posts/2025-09-08-mcp-registry-preview/). It is open source, and it is backed by a serious group: Anthropic, GitHub, PulseMCP, Microsoft, and others. Its job is deliberately narrow. In the maintainers' own framing, it is a centralized metadata repository, and the metadata it stores is intentionally unopinionated. It does not rank servers, and it does not review or rate them. It just holds the `server.json` records and serves them over a REST API. ![The official MCP Registry shown as a shared metadata source.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/i22v87okxx5uvd00vqpj.png) Here is the design choice that matters most, and almost nobody mentions it. The official registry is not really built for your app to read directly. It expects to be consumed by "downstream aggregators", which pull its data on a schedule (say, once an hour) and then layer their own curation, ratings, or search on top. The registry is the wholesale source of truth. The nice front-ends you actually browse are meant to be built by other people. And in practice, as of 2026, most MCP clients still do not read the registry directly as a built-in source. So for now you usually meet it through one of those aggregators, not through your agent going and querying it live. Two more things worth knowing early. First, the official registry does not host private servers, so an internal `mcp.acme-corp.internal` server has no place there. Second, it is in preview, which means data can reset and the shape can still change. So treat it as a fast-moving foundation, not a finished product. ## MCP Registry vs Marketplace vs Gateway This is the question I see confused most, so let me draw the lines hard. These are three different things doing three different jobs. A **registry** is discovery in its rawest form. It answers one question: what servers exist, and how do I connect to them? It is a passive list. A **marketplace** is discovery made pleasant. Sites like [Smithery](https://smithery.ai), Glama, and mcp.so give you search, categories, install buttons, and community ratings. Increasingly they read from the official registry underneath and add their own polish on top. A marketplace is a registry with a nice storefront and an opinion. A **gateway** is something else entirely. It is not about finding servers, it is about controlling them at runtime. A gateway sits in the traffic path between your agents and your MCP servers, and it enforces the rules every time a call is made: who is allowed to use this tool, how often, at what cost, with what data. A registry is a directory. A gateway is a checkpoint. ![A registry directory, marketplace storefront, and runtime gateway checkpoint.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/uuwk5bjxm48xciuypotn.png) The one line to remember: a registry without a gateway gives you discovery but no runtime governance. Your agents can find tools, but nothing stops an unauthorized call once they connect. Hold onto that, because it is the seam the rest of this post walks through. ## How Discovery Actually Works So how does a client go from "I need a GitHub server" to a running connection? Through the registry API and the `server.json` record, and the interesting part is how it proves a server is who it claims to be. ![MCP server discovery from verified namespace to connection.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/zednfcf3a863ewj396eg.png) Server names use a reverse-DNS format: `io.github.username/server-name`, or `com.example/server`. That name is not decorative. It ties the server to a namespace that the publisher had to prove they own, through GitHub or DNS verification. If you publish under `io.github.swpn0neel/*`, the registry made you show you actually control that GitHub account first. So a name is a small ownership claim, verified at publish time. Here is the piece that makes the whole thing extensible. The registry ships an [open OpenAPI spec](https://modelcontextprotocol.io/registry/about), which means anyone can stand up their own registry that speaks the exact same interface. A public marketplace can implement it. A company can implement a private one for internal servers. Any MCP client that already knows how to talk to the official registry can talk to those too, with no extra work. One shape, many registries. That design is genuinely clever, and it is also exactly why governance cannot live at this layer. A standard that lets anyone publish, and lets anyone run a compatible registry, is a standard optimized for reach, not for safety. Openness and vetting pull in opposite directions, and the registry picked openness on purpose. ## Discovery Is the Easy Half So that is discovery, more or less solved. You have a standard catalog, verified namespaces, an API, and a way to run your own copy. But discovery was always the easy half of the title. The hard half is governing what those servers actually do once your agents start calling them, and that is a problem a catalog structurally cannot touch. Let me show you why with one concrete example. ![A discovery catalog ending before runtime governance begins.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/a8xm1mq9er7negb7t9pk.png) ## What a Registry Does Not Protect You From Say you tell your coding agent, "add the Slack MCP server." It searches, finds a package named `slack-mcp-server`, sees an install command that looks completely official, and wires it in. Feels routine. Now walk that one action through each layer and watch where it does, and does not, get stopped. ![A verified registry entry beside a hidden security risk.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3ipt2u41nve3knvc35mw.png) **Did the registry catch it?** No, and this is by design. The official registry [delegates security scanning](https://modelcontextprotocol.io/registry/about) to the underlying package registries and to downstream aggregators. Its own job is namespace authentication and metadata hosting. So namespace verification proves the publisher owns the name they used. It does not prove the code behind that name is safe. A registry entry is a verified business card, not a background check. **So typosquatting still works.** This is not hypothetical. Security researchers have already documented [look-alike PyPI packages](https://www.speakeasy.com/resources/mcp-tool-poisoning) like `slack-mcp-server` and `slack-mcp-server-v2`, whose install commands look official but are not. A name that reads as legitimate is not the same as a source you vetted, and a catalog optimized for discovery will happily list both. **And the tool itself can lie.** The nastier class is tool poisoning: a malicious server hides instructions inside a tool's description, so when the model reads "what can you do?", it also reads "and quietly send recent messages to this URL." The model sees a normal tool. The registry saw a normal `server.json`. Neither one inspected behavior, because inspecting behavior is not what either one does. **Then there is the rug pull.** A server can be perfectly clean the day it is approved and turn malicious three versions later. A registry records the new version. It does not re-judge it. This exact risk is why security researchers keep arguing that tool updates should [trigger re-review](https://arxiv.org/pdf/2506.01333) instead of being trusted automatically. None of this means the registry is broken. It means the registry is doing its one job, discovery, and quietly handing you a job it was never built to do: deciding what to trust, and enforcing that decision on every call. That job has to live somewhere else. ## Do You Need a Private MCP Registry? For a lot of teams, the first real answer to the trust problem is a private registry. And it genuinely helps, so let me be fair to it before I tell you where it stops. A private, internal registry is an allowlist. Instead of letting agents pull from the open catalog, your platform team curates a list of vetted servers, mixes in your own internal ones, and points every agent at that. Because it implements the same OpenAPI shape, your existing clients keep working. This is the model enterprise registry products (Kong's, Portkey's, and others) are built around, and for regulated or multi-team setups it is close to mandatory, because a community catalog was never meant to carry your compliance requirements. ![A private registry as a curated internal MCP server allowlist.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/u9rcbgnto1tedc55k1jr.png) So a private registry moves the trust decision to a place you control. That is a real upgrade over pulling from the open internet. But look closely at when it acts. A private registry governs discovery time: what a developer or agent is allowed to install and see. It does not govern runtime: what an approved server actually does on each individual call. Once an agent is connected to a vetted server, the registry is out of the loop. It cannot cap how much that agent spends, cannot say "this key may read files but never delete them," and cannot stop a poisoned tool description that slipped through your review. It curated the shelf. It is not standing at the counter. You still need something in the traffic path. ## Where the Gateway Comes In Here is the honest part first, because it decides whether you should even read this section. If you are running one or two MCP servers you trust, you do not need a gateway. A registry, or even a hardcoded config, is plenty. A gateway earns its place when you have many servers, many agents, and real consequences if one of them misbehaves. And a gateway cannot vet server code for you either, so nothing here removes the need to review what you run. For a few months I was the first layer of testing for an AI co-worker that lived inside Slack, catching its mistakes before the real users did. The lesson that stuck with me is simple: you cannot govern what you only hear about after the fact. Governing has to happen at runtime, in the path, while the call is being made. What a gateway adds is the one thing a registry structurally cannot: enforcement on every call, at runtime. That is where [Bifrost](https://www.getmaxim.ai/bifrost), an [open-source AI gateway](https://github.com/maximhq/bifrost) built by Maxim AI, fits the picture. It can act as an [MCP gateway](https://docs.getbifrost.ai/mcp/gateway): you connect all your MCP servers to Bifrost, and your agents connect to Bifrost instead of to each server directly. One endpoint in front of everything, sitting exactly where the checks need to happen. ![An MCP gateway controlling calls between agents and servers.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/qs9x9rz2fbirqpw1blod.png) Once you are in that position, three things change. First, access stops being all-or-nothing. Bifrost filters tools per virtual key, so a given key only sees the tools you explicitly listed for it. You configure it plainly: a client named `filesystem`, with `tools_to_execute` set to `["read_file", "list_directory"]`, and that key can do exactly those two things and nothing else. It is deny-by-default. The read-only agent literally cannot see a delete tool, so a poisoned or over-eager call has nothing to grab. Second, the token problem gets solved instead of tolerated. Remember how a registry lets you connect dozens of servers? Once you do, every request stuffs every tool's description into the model's context, and with 150-plus tools the model burns most of its budget just reading the menu. Bifrost's [Code Mode](https://docs.getbifrost.ai/mcp/code-mode) fixes this by exposing four generic meta-tools and letting the model write code to discover and run what it needs on demand, instead of pre-loading every definition. In their own benchmark of 508 tools across 16 servers, that cut input tokens by up to 92.8%, from 75.1M tokens down to 5.4M, while keeping a perfect pass rate. That is a vendor benchmark on a vendor harness, so weigh it as one, but the mechanism is sound and the direction is not subtle. Third, control lives in one place. All your keys and connections sit behind the gateway instead of scattered across config files, and you can attach [budgets and rate limits](https://docs.getbifrost.ai/features/governance/virtual-keys) to each virtual key, so an agent that goes haywire hits a spending cap instead of your invoice. This is the runtime governance the registry handed off. The registry told your agents what exists. The gateway decides what is allowed, every single time. So the two are not competitors. A registry and a gateway are the two halves the title promised, and they sit at different points in the flow: the registry at discovery, the gateway at runtime. ## What Do You Actually Need? Let me put it as plainly as I can, because the whole post comes down to one distinction. A registry answers "what exists?" A gateway answers "what is allowed?" You will eventually want both, and the mistake I see most is assuming that a shiny catalog, public or private, has solved the second question. It has not. It was never trying to. ![A practical path from discovery to runtime governance.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/l26d4gjc9hd97mgyq20n.png) So here is the path I would actually take. If you are just exploring, use the official MCP registry or a marketplace to find good servers, and keep the count small. If you are a team standing this up for real, add a private registry so you control what gets discovered in the first place. And the moment agents are making real calls with real credentials and real budgets, put a gateway in the traffic path, because that is the only layer that can enforce a decision at the moment the decision matters. Discovery is a catalog problem. Governance is a runtime problem. Solve them in the right places and MCP stops feeling scary. Try to solve governance with a directory, and you will find out the hard way that an index card never stopped anybody from doing anything. I run coding agents like Claude Code every day, and my own MCP config has quietly grown from one server to a small pile without me really deciding to. That drift is exactly how this bites you, one harmless-looking addition at a time. So if you have hit the same sprawl, I would genuinely like to hear how you are handling it. Drop a comment, or come find me on [X](https://x.com/swapnoneel123) and let's compare setups. ## What Is an MCP Server? A Beginner's Guide URL: https://www.swapnoneel.site/blog/what-is-an-mcp-server Date: 2026-09-02T18:48:39.000Z Summary: What is an MCP server, in plain words? How the Model Context Protocol works, what MCP servers do, MCP vs API, and when you need a gateway. An MCP server is just a small program that gives an AI model access to tools and data it cannot reach on its own. In plain words, it is a bridge. Through one shared standard called the Model Context Protocol, it lets an AI read your files, query a database, or call an API without you having to write custom glue code every single time. It can be a tiny script running on your laptop, or a service running in the cloud. None of this is as complicated as the acronym makes it sound. And it is worth getting right, because MCP is quickly becoming the default way AI agents interact with the real world. So, let's dive in! ## What Is an MCP Server An MCP server is a program that exposes three things to an AI model: tools it can run, data it can read, and prompt templates it can reuse. Simply put, the model connects to the server, asks "what have you got?", and then uses whatever comes back. Strip away the jargon and an MCP server is a standard adapter between one AI and one outside system. The "server" word trips people up, so let me kill that confusion first. It does not mean a rack in a data center. A filesystem MCP server can be a tiny script on your own laptop. It is called a server only because it serves requests, the same way your local dev server does when you run `npm run dev`. Small program, answers questions, done. ![A small program running on a laptop opens a compact drawer of tools, readable data, and reusable prompts.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/anj3y8v5zl4rj4mqz44c.png) And that is really the core of what people mean by MCP server meaning: it is the thing on the other end of the connection that knows how to do actual work, like reading a file or querying a database, and knows how to describe that work in a format the model understands. ## Where MCP Came From and Why It Caught On The Model Context Protocol is an open standard for connecting AI models to outside tools and data. Anthropic [released it in November 2024](https://www.anthropic.com/news/model-context-protocol), and within a year it went from a niche idea to something Claude, ChatGPT, Cursor, and VS Code all speak. Here is the problem it fixed. Before MCP, if you wanted an AI to use some tool, you wrote the glue yourself, every single time. A couple of years back at Keploy I built a RAG chatbot over their docs, wiring the vector embeddings and the retrieval together by hand. That was one data source and one integration, fully custom. Now picture doing that again for GitHub, then Slack, then your database, then for a different model that expects a different shape. It does not scale, and everyone was rebuilding the same connectors. The official docs describe MCP as [a USB-C port for AI](https://modelcontextprotocol.io/introduction). One connector shape, and anything can plug into anything. You build an MCP server once, and every client that speaks the protocol can use it. That is the entire reason it caught on so fast: it turned a pile of one-off integrations into one standard everyone could share. ![Incompatible custom connectors resolve into a row of identical standard plugs.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/39n9a5s3scezxm38907y.png) ## How Does an MCP Server Actually Work? An MCP server works through three roles: a host, a client, and a server. The host is the app you are actually using, like Claude Desktop or Cursor. For each server it wants to talk to, the host spins up one client, and that client keeps a dedicated line open to one MCP server. Messages go back and forth as [JSON-RPC 2.0](https://www.jsonrpc.org/specification), which is a plain, boring, well-understood format, and that is a good thing. The server can offer three kinds of thing, and it is worth knowing them by name: - **Tools**: actions the model can run, like "read this file", "run this query", or "create this GitHub issue". - **Resources**: data the model can read for context, like a file's contents or a database schema. Read-only, no side effects. - **Prompts**: ready-made templates for common tasks, so the model does not start from scratch every time. The flow itself is simple. The client connects, then asks the server to list what it has (a `tools/list` call), then runs one of them (a `tools/call`). In JSON it looks about like this: ```json // "What can you do?" { "jsonrpc": "2.0", "id": 1, "method": "tools/list" } // "Okay, do this one." { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "get_weather", "arguments": { "city": "Kolkata" } } } ``` ![A client inside an application exchanges a capability menu, tool call, and result with a server.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/6h5paxmrnp4uqsebb6x1.png) One more thing worth knowing early: a server runs in one of two places. A local server talks over `stdio` (standard input and output), which is just two programs on the same machine passing text back and forth, no network involved. A remote server talks over HTTP, so it can live anywhere and serve lots of clients at once. Same protocol either way, which is the point. ## MCP Server vs API: What's the Difference This is the question I get most, so let me be direct. An MCP server usually sits on top of a normal API. It is not a replacement for one. The difference is who the thing is built for. A regular API is built for a developer who already read the docs and hardcoded the exact calls. It assumes someone knows the endpoints ahead of time. An MCP server is built for a model that shows up knowing nothing, asks what is available, and figures out which tool to use on its own. That runtime discovery is the whole trick, and a plain API does not do it. So when people ask about MCP vs API, the honest answer is that most MCP servers are a thin wrapper around an API you could have called yourself. What MCP adds is a standard way to describe the tools, so any model can find them and use them without a human wiring each call. The API does the work. The MCP server makes that work legible to an AI. ![An API executes fixed calls; an MCP wrapper makes those same capabilities discoverable to an AI.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/l74ofosewn670mplaspz.png) ## What Can You Actually Do With One Plenty, and you probably already have the pieces. The most common MCP servers connect a model to everyday systems: a filesystem server for reading and writing files, a GitHub server for issues and pull requests, a Postgres server for querying a database, a Slack server for messages. Anthropic keeps a set of [reference servers](https://github.com/modelcontextprotocol/servers) on GitHub, and most companies now ship an official one for their own product. I run Claude Code every day, and that is where most people meet their first MCP server. You bolt one onto a coding agent or a desktop chat app, and suddenly it can touch your files, search the web, or open a pull request without you copy-pasting anything. The MCP tools it offers just show up as things the model can now do. ![Four practical capabilities: edit files, work with repositories, query data, and send messages.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/htd147sayh27qovbfh4s.png) The pattern is always the same. Find a server for the system you care about, point your client at it, and the model gains a new skill. One server is genuinely a five-minute setup, and it feels a little bit like magic the first time an agent edits a real file on your disk! ## Do You Need an MCP Gateway Here is the honest part first: if you are running one or two MCP servers, you do not need a gateway. It solves a problem you do not have yet, so skip this section and go play with a server instead. But things change once the count goes up. Say you have got ten servers wired into your agents. Three problems show up fast. First, every request dumps the full tool list of every server into the model's context, so with 150-plus tools the model burns most of its budget just reading the menu. Second, each server has its own keys and auth, scattered across config files with no single place to rotate them. And third, there is no shared way to say who is allowed to call what, or to cap spending. That is tool sprawl, and it turns a neat setup into a liability. An MCP gateway fixes this by sitting right in the middle. Think of it like a power strip. Instead of plugging ten different cables into ten wall sockets, your AI agent connects to the gateway once. The gateway handles the rest. ![Growing server connections share one controlled gateway with centralized credentials, permissions, and budgets.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/2drkhh86pse6ywt0qc93.png) This is where [Bifrost](https://www.getmaxim.ai/bifrost), an [open-source AI gateway](https://github.com/maximhq/bifrost) built by Maxim AI, comes in. It can [act as an MCP gateway](https://docs.getbifrost.ai/mcp/gateway), tying all your servers together so your AI talks to one neat endpoint instead of juggling ten. It also fixes that token problem in a really clever way. Remember how 100 tools eat up your context? Normally, the model has to re-read every single tool's description on every prompt just to know what's on the menu. That burns tokens fast. Bifrost fixes this using [Code Mode](https://docs.getbifrost.ai/mcp/code-mode). Instead of dumping every tool definition into the prompt, it gives the model four generic tools and lets it write code to find and run what it needs on demand. In their benchmarks, that cut input tokens by up to 92.8%. That is not a small difference, that is basically your whole bill! Very similar to the cut-costs-at-the-gateway idea I talked about with [semantic caching](https://www.swapnoneel.site/blog/what-is-semantic-caching). On top of that, you get one central place for safety and control. All your API keys live in one spot instead of being scattered across random config files. You can set [per-key tool filtering and budgets](https://docs.getbifrost.ai/features/governance/virtual-keys), meaning you decide exactly which agent can use which tools and how much it can spend. And by default, tools do not run automatically unless you explicitly turn that on. That last part matters a lot, because an agent running tools silently without your permission is a bad day waiting to happen. ## Where to Start You do not need to read a massive spec or write complex code to get started with MCP. The whole idea is small once the acronym stops scaring you: it is just a standard plug that lets your AI reach outside tools. If you want to try it out today, do not overthink it. Pick an app you already use, like Claude Desktop, Cursor, or Claude Code. Then grab just one pre-built server, like the filesystem or GitHub server. Wiring it into your config file takes about five minutes. Once it is running, give the model a real prompt: ask it to inspect a local folder or check a repo. The moment you see an agent reach outside its chat box and touch a real file on your machine, the whole protocol clicks. ![Choose an app, connect one filesystem server, and complete one real task before expanding.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/8ovdnlvnm2h0i8g3p7zt.png) And my advice would be to keep it tiny. You do not need a gateway or fancy routing when you are only running one or two servers. Play with one first, see how it feels, and only put a gateway like Bifrost in front when you have a dozen servers and your token bill starts climbing. If you set up your first MCP server after reading this, I would genuinely like to hear how it went. Drop it in the comments, or come say hi on [X](https://x.com/swapnoneel123) and we can chat about it. ## Frequently Asked Questions (FAQs) **What is an MCP server in simple terms?** It is a small program that gives an AI model a set of tools and data through a shared standard, so the model can read a file, query a database, or call an API without custom glue code. Think of it as a standard adapter between one AI and one outside system. **What is the difference between an MCP client and an MCP server?** They are the two ends of one connection. The server offers tools, data, and prompts. The client, created by an app like Claude Desktop, connects to the server and uses what it offers. One app can run many clients, one per server it talks to. ![A compact field guide clarifies the adapter, client/server relationship, permissions, and building a small server.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/uqm68vyb9mbz3kdjgz25.png) **Are MCP servers safe to use?** They are as safe as how you run them. A single local server you trust is low risk. The danger shows up when you run many unvetted servers with their own credentials and let an agent run tools automatically, which is exactly why keeping auto-execution off and routing servers through a gateway with tool filtering is worth doing once you scale. **How do you build your own MCP server?** You use one of the official SDKs, define a few tools with their inputs, and expose them over `stdio` or HTTP. Start tiny, with one or two tools, get it working with a client you already use, and grow from there instead of trying to wrap an entire API on day one. ## Fixing Claude Rate Limit Errors with an AI Gateway URL: https://www.swapnoneel.site/blog/claude-rate-limit-error Date: 2026-08-26T18:04:49.000Z Summary: A Claude rate limit error is five different failures wearing one status code. How to tell them apart, retry correctly, and route around the ceiling. Ever had a `429` from Claude that just would not clear, no matter how patiently you backed off? Yeah. That one was never a rate limit. A Claude rate limit error is an HTTP `429` carrying `"type": "rate_limit_error"`, and it means your organization crossed a per-minute request ceiling, a per-minute token ceiling, or its monthly spend cap. Which one decides the fix, and the response body tells you which before you write any retry code. And the difference matters far more than it sounds, because two of those three conditions get strictly worse the moment you retry them. ## The five failures behind one Claude rate limit error `rate_limit_error` is not one condition. Anthropic returns the same string for several different situations, and the correct response may be to wait a few seconds or to change a setting that retrying cannot fix. ![Diagnostic flow for five Claude API failure types](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/46c0m6i2doo5k0h0c299.png) The [Claude API errors reference](https://platform.claude.com/docs/en/api/errors) documents them separately, so you can tell them apart from the response alone. | What you see | What it actually is | Does retrying help? | | --- | --- | --- | | `429` + `rate_limit_error` + a `retry-after` header | A real per-minute limit. You went too fast. | Yes, after `retry-after` seconds | | `429` + `rate_limit_error` + `details.error_code: enforced_spend_limit_reached`, and no `retry-after` | Your usage tier's monthly spend cap | No. Access returns at 00:00 UTC on the 1st | | `400` + `invalid_request_error`, message starting `You have reached your specified API usage limits` | A spend limit you set yourself in the Console | No. Raise or remove your own limit | | `429` right after a sharp traffic ramp | An acceleration limit, not your steady-state limit | Partly. Ramp up gradually instead | | `529` + `overloaded_error` | Anthropic is busy. Nothing to do with you. | Yes, but the queue is global | That last row is the one that trips people up most. The Claude overloaded error gets searched more than any specific 429 phrasing, and almost every guide lumps it in with rate limits. It isn't yours. A `529` means the API is temporarily overloaded across all users, so no amount of tier upgrading or key rotation on your side moves it. The second row is easy to misdiagnose. It looks identical to a normal rate limit at the status-code level, the official SDKs will happily auto-retry it, and every retry fails. The tell is the missing `retry-after` header plus that `error_code`. So before anything else, log the full error body. Not `err.status`. The body. ```python import anthropic client = anthropic.Anthropic() try: msg = client.messages.create( model="claude-sonnet-5", max_tokens=1024, messages=[{"role": "user", "content": "hi"}], ) except anthropic.APIStatusError as e: print(e.status_code) # 429 print(e.response.headers.get("retry-after")) # None means spend cap print(e.body) # the error_code lives here print(e.request_id) # req_018Ee... for support ``` The `retry-after` check on that second line is doing almost all of the diagnostic work. Present means wait. Absent on a `429` means stop retrying and go look at billing. ## What actually counts against your Claude rate limits Now, the per-minute limits. There are three of them running at once, per model, and you can trip any one. ![Claude rate limit token buckets and prompt caching](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/hmklnlthq7ogxtpyf8hz.png) - **RPM**, requests per minute. - **ITPM**, input tokens per minute. - **OTPM**, output tokens per minute. They are enforced per model class, so Sonnet traffic and Haiku traffic draw from separate buckets and can run at full speed at the same time. Worth knowing: on the current lineup, Sonnet 5 has its own bucket while Sonnet 4.6 and 4.5 share a combined one, and the same split applies to Opus 5 versus the Opus 4.x family. Pinning half your traffic to an older Sonnet does not double your Sonnet 5 headroom. The [rate limits documentation](https://platform.claude.com/docs/en/api/rate-limits) also says something that should change how you think about bursts. The limiter is a token bucket, which means capacity refills continuously instead of resetting on a clean minute boundary. A 60 RPM limit behaves much more like 1 request per second than like 60 free requests at the top of every minute. So if you fan out 60 concurrent calls at 12:00:00 you will get rate limited, even though your average for that minute was exactly at the ceiling. I hit this exact shape of problem building [Scholarian](https://scholarian.vercel.app), which fetched and ranked over 10,000 academic papers. The moment you write a loop that fans out one model call per document, your average throughput looks perfectly fine on a dashboard and your p99 latency (the slowest 1% of calls) is a wall of 429s, because nothing in that loop is pacing itself. ### The one number most people get wrong For most Claude models, only uncached input tokens count against ITPM. Specifically: `input_tokens` and `cache_creation_input_tokens` count, and `cache_read_input_tokens` does not. So a cache hit is free as far as your rate limit is concerned, and it is billed at a reduced rate on top of that. (Claude Haiku 3.5 is the exception and does count cache reads.) Do the arithmetic on that, because it is not a small effect. Say you are on the Start tier with 2,000,000 ITPM on Sonnet 5, and you are running a coding agent that ships roughly 180,000 tokens of context per turn. Without caching, 2,000,000 divided by 180,000 is about **11 turns per minute** for your entire organization. With prompt caching where 170,000 of those tokens are cache reads, only 10,000 counts against ITPM. That is **200 turns per minute** off the exact same limit! Anthropic's own worked example frames it the same way, and it is why they publish your cache hit rate on the Usage page right next to the rate limit charts. One nuance people miss: `cache_creation_input_tokens` does count. Writing the cache costs you full ITPM once, and reading it is free. So a workload that keeps rebuilding the cache because the prefix keeps shifting gets the worst of both. Output is simpler. OTPM is measured on tokens actually produced, in real time, and `max_tokens` never factors into it. There is genuinely no rate limit penalty for setting a generous `max_tokens`, so stop shaving it down for that reason. ## The headers you should be reading instead of guessing Every response, including the successful ones, carries your current standing. And most people never look at them. ![Monitoring Claude rate-limit response headers](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/8fsszd8u7gfiw96tlmhi.png) ```bash curl -sS -D - -o /dev/null https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{"model":"claude-sonnet-5","max_tokens":16, "messages":[{"role":"user","content":"hi"}]}' ``` Run that and you get back a block of `anthropic-ratelimit-*` headers. The ones worth wiring into a metric: - `anthropic-ratelimit-requests-remaining` - `anthropic-ratelimit-input-tokens-remaining` - `anthropic-ratelimit-output-tokens-remaining` - the matching `-reset` fields, which are RFC 3339 timestamps rather than durations Two gotchas here. The `remaining` values are rounded to the nearest thousand, so treat them as a gauge and not as an accountant. And the generic `anthropic-ratelimit-tokens-*` triplet reports whichever limit is currently most restrictive, which means the number can jump between input and output accounting between requests without anything being wrong. Identify the failure and read the remaining-capacity headers before changing retry logic. Backoff cannot fix a spend cap, and tuning it first wastes time. At one of my previous roles I built an internal tool that pulled the data logs off an AI product and turned them into latency and slowdown reports, and the useful part was never the clever part. It was just having the numbers somewhere you could look at them without reproducing the bug first. Same idea here. Export those three headers as gauges and most rate limit debugging stops being detective work. ## Retrying correctly, and where retrying stops helping ![Retry backoff with jitter and hard ceilings](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/izmrdm5d9upjquptckub.png) The rule in plain English: if the response gave you a `retry-after`, wait exactly that long. If it did not, double your wait each attempt and add randomness so your workers do not all wake up at the same instant. ```python import random, time, anthropic client = anthropic.Anthropic(max_retries=0) # we handle it ourselves def call_with_retry(**kwargs): for attempt in range(5): try: return client.messages.create(**kwargs) except anthropic.APIStatusError as e: if e.status_code not in (429, 500, 502, 503, 504, 529): raise hinted = e.response.headers.get("retry-after") if e.status_code == 429 and hinted is None: raise # spend cap, so retrying is pointless wait = float(hinted) if hinted else min(2 ** attempt, 30) time.sleep(wait * random.uniform(0.8, 1.2)) raise RuntimeError("exhausted retries") ``` The jitter multiplier on the last line is the bit people skip, and it is the bit that matters at concurrency. Without it, 50 workers that all got rate limited at the same moment will all retry at the same moment, and you get a thundering herd that reproduces the original 429 on a fixed schedule forever. Also notice `max_retries=0` at the top. The official SDKs retry twice by default with their own backoff, honoring `retry-after`. That default is fine for a script. It is not fine when you are also running your own retry loop, because the two layers multiply and you quietly end up with 3x the attempts you thought you configured. But here is the honest limit of all of this, and it is why the post does not end here. Retries are a queueing strategy. They smooth out bursts against a ceiling you are near. They do nothing at all when you are structurally above the ceiling, because every retry is just another request competing for the same bucket. If your steady-state demand is 3,000,000 input tokens per minute and your limit is 2,000,000, no backoff curve in the world fixes that. You need more ceiling. ## Setup is done, now the part that actually raises the ceiling Everything above happens inside one API key against one endpoint. Reading the error properly and then caching aggressively will genuinely resolve most Claude rate limit errors, and if you are a solo developer on one key, honestly stop reading here and go turn on prompt caching. You will get more out of that one afternoon than out of any infrastructure. ![Four independent sources of Claude API capacity](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ipw85rkw6py2uyu78m46.png) But if you are past that point, the interesting fact is that "Claude capacity" is not one pool. It is four, and they are metered by different organizations running different quota systems. ### Four separate places Claude capacity lives **1. The direct Claude API, by usage tier.** Start, Build, Scale, then Custom. Each tier carries both per-minute limits and a monthly spend cap, and as of mid-2026 those caps sit at 500USD on Start, 1,000USD on Build, and 200,000USD on Scale. Tiers move up automatically as you build usage history, and you can also request an increase from the Console. **2. Amazon Bedrock.** Claude on Bedrock runs against [AWS service quotas](https://docs.aws.amazon.com/bedrock/latest/userguide/quotas.html), which are per-account and per-region and adjustable through the Service Quotas console. These have no relationship to your Anthropic tier at all. Bedrock even splits its own quotas across two inference endpoints, tracked separately for the same underlying model. **3. Google's platform.** Claude models run there through the model garden against Google Cloud quotas, again on a completely separate meter. **4. Priority Tier.** Anthropic sells committed capacity with its own `anthropic-priority-*` headers and its own bucket, sitting alongside your standard limits rather than replacing them. The consequence is the interesting part. An organization pinned at 100% of its direct-API ITPM might have an entirely idle Bedrock quota sitting in two regions. The capacity exists, and it is already paid for. The problem is that your application has one base URL and one key, so it cannot reach any of it. One clarification before moving on, because this is the most common mix-up of all. If your error reads `API Error: rate limit reached` inside Claude Code on a Pro or Max plan, none of this section applies to you, because that is a subscription meter and not the API. Two different systems, two different clocks, and I wrote about [Claude Pro usage limits](https://www.swapnoneel.site/blog/claude-pro-usage-limits) separately. ## Where a gateway changes the math This is the job an [AI gateway](https://www.getmaxim.ai/bifrost) does, which you will also see called an LLM gateway or an LLM proxy. It is a proxy that speaks every provider's API shape, sits between your code and the model, and holds the routing decisions your application shouldn't have to. ![AI gateway routing across keys and providers](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ti2ch28898n2suxkmr9i.png) Let me lead with what it cannot do, because that part gets oversold constantly. A gateway does not raise Anthropic's ceiling. Your Start tier is still your Start tier. If you run one Anthropic key through a gateway and change nothing else, you will hit exactly the same 429 at exactly the same token count, plus a few microseconds of hop. What it changes is how many buckets a single request can reach, and how fast it gives up on a bad one. [Bifrost](https://docs.getbifrost.ai/overview) is Maxim AI's high-performance, [open-source AI gateway](https://github.com/maximhq/bifrost) that unifies access to 20+ providers through a single OpenAI-compatible API. Its [retry and fallback layer](https://docs.getbifrost.ai/features/retries-and-fallbacks) distinguishes upstream failures from credential failures: it retries `5xx` and network errors against the same key, rotates keys for `429`, `401`, `402`, and `403` responses, and moves to the next provider only after the current provider's retry budget is exhausted. That behavior maps directly to the different Claude failure modes in this post. ### Pooling keys so one bucket is not the whole story Rate limits are set at the organization level, so a second organization with its own key is a second bucket. A gateway can treat several keys as one logical pool. ```json { "providers": { "anthropic": { "keys": [ { "name": "primary", "value": "env.ANTHROPIC_API_KEY", "models": ["*"], "weight": 0.7 }, { "name": "overflow", "value": "env.ANTHROPIC_API_KEY_2", "models": ["*"], "weight": 0.3 } ] } } } ``` Traffic splits 70/30 by weight. But the part that matters for 429s is what happens on failure. Bifrost rotates keys when the failure is bound to the credential rather than to the request, which it defines as `429`, `401`, `403` and `402`. A rate limited key gets marked used for that cycle and the request carries on against another key. Auth and billing failures mark the key dead for the remainder of that request. Once every key has been tried, the set resets and a fresh weighted round begins. Key selection is scored rather than round robin, using recent error rates, latency, and observed rate limit hits, with weights recomputed on a short interval and penalties decaying once a key recovers. That is [adaptive load balancing](https://www.swapnoneel.site/blog/what-is-adaptive-load-balancing) applied to credentials instead of servers, and it matters here because a key that just returned a `429` is a bad destination for the next 30 seconds specifically, and not permanently. ### Falling back across channels, not just keys Key pooling only helps if you have more keys. Falling back across the four capacity pools helps even when you don't. ```json { "model": "anthropic/claude-sonnet-5", "messages": [{ "role": "user", "content": "..." }], "fallbacks": [ "bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0", "vertex/claude-sonnet-4-5@20250929" ] } ``` Same request, same Anthropic model family, three completely independent quota systems. If the direct API is capped, the call lands on Bedrock's per-region quota, which knows nothing about your Anthropic tier. The execution detail worth knowing is that each provider in the chain gets its own full retry budget. A primary at `max_retries: 3` with two fallbacks also at 3 means up to 12 attempts on one call, so set your client timeout with that in mind. [Retries and fallbacks](https://docs.getbifrost.ai/features/retries-and-fallbacks) documents the backoff as `min(initial × 2^attempt, max) × jitter(0.8-1.2)`, with defaults of 500ms initial and a 5000ms cap. And a real gotcha, stated plainly because it will bite you: `max_retries` defaults to 0. Retries are off until you turn them on. ```json { "network_config": { "max_retries": 3, "retry_backoff_initial": 500, "retry_backoff_max": 5000 } } ``` ### Two more things that buy headroom **Caching at the gateway instead of per service.** If four services ask the same question, an application-level cache inside each one misses four times. A gateway cache hits three of them, and every hit is a request that never touches your ITPM at all. Bifrost's version only engages when a request carries an `x-bf-cache-key` header, defaults to a 5 minute time-to-live and a 0.8 cosine similarity threshold, and can run in exact-match mode with no embedding provider at all. That threshold is the whole product, and I went into why in a post on [semantic caching](https://www.swapnoneel.site/blog/what-is-semantic-caching). **Stopping one tenant from eating the org bucket.** Your organization limit is shared, so one runaway batch job starves the interactive traffic sitting behind it. Governance limits let you cap consumption per virtual key before the request ever reaches Anthropic, in tokens or in requests, with calendar-aligned reset periods running from `1m` up to `1Y`. When a cap trips, the gateway reports the breach with its own code instead of a vague 429: `token_limited` or `request_limited` on a `429`, `budget_exceeded` on a `402`, and `403` for a blocked model or provider. The [governance docs](https://docs.getbifrost.ai/features/governance) carry the full mapping. That is a real difference in debuggability. "Your batch job hit its token cap" is a fix you can act on in a minute. "Anthropic said 429" is an afternoon. ### Pointing Claude Code at it Since a lot of people arriving at this error are inside a coding agent rather than a Python service, the integration is two environment variables. Bifrost exposes an Anthropic-compatible endpoint, so the client never knows anything changed. ```bash export ANTHROPIC_BASE_URL=http://localhost:8080/anthropic export ANTHROPIC_API_KEY=dummy-key ``` The path is `/anthropic`, and not `/v1/anthropic`. And the key can be a placeholder because the real credentials live inside the gateway. Gateway traffic is billed per token against API keys. It uses a separate meter from Pro or Max subscriptions, not a larger subscription allowance. ## What a gateway will not fix Four things, because a post that only lists the wins is a brochure. ![Limits an AI gateway cannot solve](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/4xkzvwwu4uwqzxsaiuvz.png) It does not help with a `529`. An overloaded error is Anthropic-wide capacity, so a fallback to Bedrock might dodge it, but no amount of key pooling on the direct API will. It does not make a cross-provider fallback behave identically. Sonnet 5 on the direct API and Sonnet 4.5 on Bedrock are not the same model, and if your prompts are tuned tightly or you lean on a specific tool-calling shape, your fallback path needs its own evals. A fallback that silently produces worse answers is arguably worse than a clean 429. It does not fix a workload that is simply too large. If you need 5x your current ceiling permanently, the answer is a tier increase or committed capacity, and routing is the bridge that gets you there without downtime in the meantime. And the vendor benchmarks are vendor benchmarks. Bifrost's page claims 20 microseconds of added latency at 5,000 requests per second, and a 50x advantage on that same p99 number over LiteLLM. Those are Maxim's own numbers on Maxim's own harness, and you should treat them the way you treat every published benchmark, which is as a reason to go run your own. ## What you should actually do, in order Ordered by what each one costs you, cheapest first. Stop at the first one that fixes it. ![Ordered checklist for resolving Claude rate limits](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/phfhxeina7wuku5szc27.png) **One.** Log the full error body and check for `retry-after`. If it is missing on a `429`, you have a spend cap and no retry strategy will ever help you. **Two.** Export the three `anthropic-ratelimit-*-remaining` headers as gauges. You cannot tune what you cannot see. **Three.** Turn on prompt caching and check your hit rate on the Usage page. This is the single biggest win available and it costs you an afternoon, because cached reads do not count against ITPM on most models. **Four.** Add `retry-after`-aware backoff with jitter, and make sure you are not stacking it on top of the SDK's own retries. **Five.** Move anything non-interactive to the Message Batches API, which has its own separate rate limits. **Six.** Request a tier increase. It is free and it is slow, so start it before you need it. **Seven.** Only now, put a gateway in front and pool keys or channels. This is real infrastructure with real operational cost, and it is worth it once you have genuinely run out of ceiling, and mostly not before. The reason I would put it dead last rather than first is that most 429s I have run into were not capacity problems at all. They were a cache that never got turned on, or a retry loop with no jitter, or a spend cap that everybody kept retrying into. Fix those and the ceiling stops being the constraint. If you have hit a `429` shape that doesn't fit any of the five rows in that table, drop it in the comments, I'd genuinely like to see it. And if you want more of this sort of thing, I'm on [X](https://x.com/swapnoneel123) and I write everything up first on [my site](https://www.swapnoneel.site). ## Claude Pro Usage Limits Explained and How to Work Around Them URL: https://www.swapnoneel.site/blog/claude-pro-usage-limits Date: 2026-08-25T17:05:36.000Z Summary: Claude Pro usage limits run on two clocks, not a message counter. What really drains them, how to read the meter, and eight ways to get more from one plan. In March 2026, Claude Max subscribers started watching a 5-hour window drain in 90 minutes. One person reported going from 21% used to 100% used on a single prompt, and everybody assumed it was a bug. It wasn't. Anthropic's Thariq Shihipar [posted on X](https://x.com/trq212/status/2037254607001559305) and said that they're adjusting the 5 hour session limits during peak hours to manage growing demand, affecting roughly 7% of users. Claude Pro meters you on two clocks at once: a rolling 5-hour session window and a weekly cap, shared across claude.ai, Claude Code and the desktop app. Neither one is a message counter. Both are token meters, and what drains them fastest is not how much you ask, it is how much you make Claude re-read. Once you read the meter properly, most of the "limits" problem becomes context management, and context is something you control. ## What are Claude Pro's usage limits, exactly? Two windows, running at the same time, and you can hit either one. ![Two usage clocks share one allowance](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/yyqtk1qqtnuyues2j9ll.png) The **5-hour session window** starts with your first message and rolls. It is not a clock that ticks at the top of the hour, and it is not tied to a calendar day. Send a message at 2pm and that window closes at 7pm, whether you sent one message in it or four hundred. The **weekly window** sits on top of that and resets at a fixed time assigned to your account. This one is the real ceiling for heavy users, because you can stay inside every 5-hour window all week and still run out on a Thursday. Both windows are shared. [Anthropic's docs]((https://support.claude.com/en/articles/11145838-using-claude-code-with-your-pro-or-max-plan)) are explicit that usage across claude.ai, Claude Code and Claude Desktop counts toward the same pool. So the hour you spent arguing with Claude about your resume in the browser is the same hour you don't get in your terminal later. And here is the part that annoys everyone, me included: **Anthropic publishes no token number for any plan.** Not for Pro, not for Max. Pro is described as more usage than free, and Max is sold as 5 times or 20 times more usage than Pro. Any blog quoting you an exact message count for Pro is guessing. The plans themselves, as of August 2026: Pro is 17USD per month billed annually or 20USD per month billed monthly, and Max starts at [100USD per month](https://claude.com/pricing). Same model access on both. The only thing you buy with Max is a bigger number on the same two clocks. There is one more distinction worth holding onto, because it changes what you do when you get blocked. A message saying you hit your **session limit** or **weekly limit** is plan-wide, and switching models with `/model` will not save you. A message saying you hit your **Opus limit** or **Sonnet limit** is model-specific, and switching to a different model family genuinely does keep you working. ## Why your limit drains faster than your typing Two developers, same Pro plan, same afternoon. **Developer A** opens a fresh Claude Code session, asks 40 short questions about a small file, and closes it. **Developer B** opens one session at 9am, works on and off all day across three unrelated tasks without ever clearing, and sends 12 messages total. Developer B burns more of the plan. [Anthropic's cost documentation](https://code.claude.com/docs/en/costs) explains why: > Claude Code sends your full conversation with every request, and each time Claude uses tools it sends another request carrying that batch of tool results. So a one-line question at 4pm, in a session that has been open since 9am, draws usage for the entire conversation sitting behind it. Every file that got read, every tool result, every previous answer. Twelve messages carrying a whole day of accumulated context beats 40 messages carrying almost nothing, every single time. ![Context weight drains the meter](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/0642lcmw6j9v4tbn0b9t.png) Three more things burn the meter while you are not looking, and none of them are obvious. **Cache misses.** Prompt caching is what keeps a long session affordable, because re-read history gets billed at the cached rate instead of the full one. But the cache has a lifetime. On a subscription it is one hour, and your first message after a longer break misses it and reprocesses your entire context at full price. That is the real answer to "why did one question cost me so much". **Extended thinking.** It's on by default, thinking tokens bill as output tokens, and the default budget can run to tens of thousands of tokens per request depending on the model. You are paying for reasoning on tasks that may not need any. **Agent teams.** If you have them switched on, they use roughly **7 times** more tokens than a normal session when teammates run in plan mode, because every teammate carries its own full context window. Five agents is five conversations, and not one. I have some personal skin in this part. I have been building [ANRL](https://anrl-site.vercel.app), an AI-native representation language with a Rust parser-compiler, specifically because delimiter overhead and context fragmentation waste an absurd share of a context window. It cuts delimiter token overhead by over 40% in my benchmarks. Spending months on that problem is what made me stop reading the limit as a quota and start reading it as a bill for context I chose to carry. ## Why did Claude cut me off after one question? Because that one question was not one question. If you resumed a large session after a long break, you paid a cache miss on the whole history. If a scheduled task or a background job fired while the session sat idle, it sent your full context along with it. If you ran `/compact`, that operation itself reads the entire conversation it is summarizing, so compacting a huge context is itself a huge request. ![A cache miss reopens the whole history](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/2r7xnd21yjazp4j7r2n0.png) The tell is always the same: the size of the request has almost nothing to do with the size of what you typed. ## When do Claude usage limits reset? The 5-hour window resets 5 hours after your first message in that window, rolling. There is no fixed reset hour, so the practical move is just to notice when you started. The weekly window resets at a fixed time tied to your account rather than a global Monday. When you actually get blocked, the error message tells you the reset time, and that is the only authoritative source you have. ![Rolling and weekly resets](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/t2h6z4udvqeugogh29f5.png) Worth knowing: Claude Code v2.1.234 and later can wait out a limit and resume the interrupted task automatically once the window resets. It lives in `/rate-limit-options`, and it beats sitting there refreshing. ## How do you see where your usage actually went? This is the step almost everybody skips, and it is the one that changes behaviour. Run `/usage` inside Claude Code. On a paid plan it shows plan usage bars plus a breakdown that is genuinely useful: - **Attribution**, meaning how much of your recent usage went to skills, subagents, plugins and each individual MCP server, as a percentage of the total. - **Behavior flags**, which name the behaviour costing you the most whenever one crosses 10% of recent usage, so "long context" or "cache misses" gets called out by name instead of you having to guess. - **Loops**, the heaviest scheduled tasks that ran recently, with per-run token counts. ![A diagnostic view of usage](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/j1duf0pqm9wh4m8sooj0.png) Press `d` or `w` to flip between the last 24 hours and the last 7 days. Then run `/context` to see what is sitting in your context window right now, and `/insights` for a report on how you actually work, written out to `~/.claude/usage-data/report.html`. One honest caveat. These numbers are computed from local session history on that machine, so usage from your other devices and from claude.ai is not in there. Don't treat the bars as your true remaining balance. I built something adjacent to this on a contract a while back, an internal tool that captured an AI product's logs and turned them into reports on latency and probable slowdowns (keeping it vague here, can't say much more than that). The lesson transferred completely: teams argue about model quality for weeks and never once look at the token accounting, which usually takes an afternoon and explains most of the pain. ## How do you actually work around Claude Pro's usage limits? Let me be blunt about one thing before the list. **Nothing here raises your subscription cap.** There is no flag and no proxy that makes Anthropic hand you more of a plan you didn't buy, and anybody selling you a "bypass" is selling you something that doesn't exist. What you can do is make each token buy more work, and give yourself somewhere to go when you get blocked. Both are real, and together they are worth more than a plan upgrade. ![Ways to stretch one plan](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/wcx40cj0bon9l2lr74x7.png) ### 1. Clear between unrelated tasks `/clear` does more work than anything else on this list, because it attacks the long-context problem directly. Switching from a bug fix to writing docs? Clear. Use `/rename` first so you can `/resume` that session later if you need it. ### 2. Match the model to the job Sonnet handles most coding work and costs meaningfully less than Opus. Keep Opus for architecture calls and multi-step reasoning, and switch with `/model` mid-session rather than committing at the start. For simple subagents, set `model: haiku` in the subagent config. Leaving Opus as your default all day is the most common way people burn a weekly cap without noticing. ### 3. Turn the thinking budget down when you don't need it Lower the effort level with `/effort`, disable thinking in `/config` for simple work, or cap it with an environment variable: ```bash MAX_THINKING_TOKENS=8000 ``` That line says: give me at most 8,000 tokens of reasoning per request. On adaptive-reasoning models the budget is ignored, so use effort levels there instead. ### 4. Shrink what loads at session start Your `CLAUDE.md` is loaded into context at the start of every single session, so a 600-line file is a tax on unrelated work all week. Anthropic's own guidance is to keep it under 200 lines and move specialised instructions into skills, which load only when invoked. Same idea for MCP servers. Run `/mcp` and switch off the ones you aren't using, and prefer CLI tools like `gh` or `aws` where they exist, since those add nothing to context until Claude actually runs them. ### 5. Push verbose work out of the main conversation Two mechanisms, same principle: keep the noisy stuff out of the conversation that gets re-sent every turn. **Subagents** for running tests and chewing through logs. The verbose output stays in the subagent's context and only a summary comes back. **Hooks** for preprocessing. A `PreToolUse` hook that greps a 10,000-line log for `ERROR` before Claude ever sees it takes that context from tens of thousands of tokens down to hundreds. ### 6. Stop paying for cache misses The cache lifetime is one hour on a subscription. So the pattern that hurts is: work for an hour, go to a meeting for 90 minutes, come back and drop a one-liner into the same session. Either come back inside the hour, or start fresh instead of resuming. On Pro and Max, Claude Code will offer to resume a large session from a summary rather than the full history, and you should take it. ### 7. Understand what usage credits really cost You can switch on usage credits and keep working past your cap, billed at standard API rates. Fine. But there's a trap in the small print that nobody mentions: **once you are drawing on usage credits, the prompt cache lifetime drops from one hour to five minutes!** So the moment you go over your cap, your long sessions get dramatically more expensive per message, and not just marginally. You can choose the TTL yourself to keep the one-hour lifetime, and if you use credits at all, you should. ### 8. Give yourself a second lane The seven above make one plan go further. This one is different, because it gives you somewhere to fail over to when the plan is genuinely spent. Claude is available from Anthropic directly, from Amazon Bedrock and from Google Vertex AI, and each of those has **its own independent rate limits**. Your subscription cap has no bearing whatsoever on your Bedrock quota. So "I'm blocked until Tuesday" is really a routing problem wearing a costume, and routing problems have known solutions. ## What an AI gateway does about this, and what it can't An AI gateway is a proxy that sits between your tools and the model providers you use. [Bifrost](https://docs.getbifrost.ai/overview) is Maxim AI's high-performance, [open-source AI gateway](https://github.com/maximhq/bifrost) that unifies access to 20+ providers through a single OpenAI-compatible API. For usage limits, the relevant features are ordered [provider fallbacks](https://docs.getbifrost.ai/features/retries-and-fallbacks) and [virtual keys](https://docs.getbifrost.ai/features/governance/virtual-keys) with their own budgets and rate limits. Together, they create a separately metered API route for overflow traffic, but they do not increase the Claude Pro subscription allowance. **A gateway cannot raise your Claude Pro cap.** Your subscription is an OAuth relationship between Claude Code and Anthropic, and no proxy in the world changes that arithmetic. Worse, the moment you point Claude Code at a gateway you are authenticating with API keys, which means that traffic bills per token and doesn't touch your subscription at all. Different cost model, and you should walk into it knowingly. Which is exactly why it works as an overflow lane. It's a separate meter, and not a bigger one. ![A separate gateway overflow lane](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ijjzcxkuo8eij5svwypk.png) Pointing Claude Code at it is two lines in `~/.claude/settings.json`, or `%USERPROFILE%\.claude\settings.json` on Windows, which is where mine lives: ```json "env": { "ANTHROPIC_BASE_URL": "http://localhost:8080/anthropic", "ANTHROPIC_AUTH_TOKEN": "your-virtual-key" } ``` The path is `/anthropic` and not `/v1/anthropic`, which is the mistake everyone makes exactly once. From there, `/model openai/gpt-5.5` or `/model vertex/claude-haiku-4-5` switches providers mid-session, and the harness never knows anything changed. The gateway adds four controls that help manage the separate API meter. **Failover that fires on the error you care about.** [Fallback chains](https://docs.getbifrost.ai/features/retries-and-fallbacks) are declared as an ordered list, and every provider in the chain gets its own full retry budget: ```json "fallbacks": [ "anthropic/claude-sonnet-4-6", "bedrock/anthropic.claude-sonnet-4-6-v1:0" ] ``` The detail I like here is how a 429 is handled. Hitting a rate limit does not immediately jump you to the next provider. It rotates to another key **within** the same provider first, and only falls through once that provider's retries are exhausted. Backoff doubles on each retry until it hits a ceiling, with a bit of randomness thrown in so a thousand clients don't all retry on the same tick. In practice that is `min(initial × 2^attempt, max) × jitter`, defaulting to 500ms initial and 5,000ms maximum, so a provider having a bad minute doesn't get hammered on the way out. **Budgets that refuse instead of warn.** [Virtual keys](https://docs.getbifrost.ai/features/governance/virtual-keys) are scoped credentials carrying their own budgets and rate limits. You give the coding agent one key and the side project another, and when a budget is exhausted the request gets refused with a real status code rather than quietly costing you money. This is the piece Anthropic's own plan structure genuinely does not give an individual. **Caching, so repeated work stops being paid work.** A cache hit is a completion you never pay for, in tokens or in seconds. I went through the mechanics, the threshold trap and the realistic hit rates in [what semantic caching actually is](https://www.swapnoneel.site/blog/what-is-semantic-caching), including why the 95% figure quoted everywhere is not a hit rate at all. **Numbers you can act on.** Every request through the gateway is logged with tokens, cost, latency and provider. Same accounting `/usage` gives you, except it survives across machines and covers the tools that aren't Claude Code. If you want the routing side in more depth, I wrote up [adaptive load balancing](https://www.swapnoneel.site/blog/what-is-adaptive-load-balancing) separately. And the overhead question has a real answer: Bifrost adds tens of microseconds per request at 5,000 requests per second, which is nothing next to a model call. ## Is Claude Max worth it, or should you just use the API? Depends on one number, and you can go get it today. Open `/usage`, press `w`, and look at what share of your weekly cap you actually consumed. If you regularly finish the week under 70%, Max is not your problem and better context habits will get you the rest. Upgrading to fix a habit is an expensive way to avoid typing `/clear`. If you are hitting the weekly wall by Wednesday every week, then it's a real capacity problem and you have two honest options. Max at 100USD per month buys you 5 times the same plan, predictably, and it's the right answer if your usage is steady. Pay-per-token through a gateway has no ceiling at all, but also no floor, so it's the right answer if your usage is spiky. ![Subscription versus metered API](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/uu54npgbb94cpswizr6u.png) For reference on the pay-per-token side, Anthropic's enterprise deployments average around 13USD per developer per active day and stay under 30USD per active day for 90% of users. On steady daily use, that math does not favour the API. On three heavy days a month, it does. My own answer, and yours might be different: subscription for the daily driver, gateway for the overflow and for everything that isn't Claude Code. ## One more thing about the limits themselves They move, and this year they have mostly moved in your favour. On 6 May 2026, Anthropic [doubled Claude Code's 5-hour rate limits](https://www.anthropic.com/news/higher-limits-spacex) across Pro, Max, Team and Enterprise, and removed the peak-hours reduction that caused the March mess. That came attached to a compute deal for more than 300 megawatts of new capacity, over 220,000 GPUs. Then weekly Claude Code limits went 50% higher as a promotion from 13 May, originally through 19 August 2026, and later extended to 31 August. Anthropic's own wording about making it permanent was careful: they hope to, but capacity may be tight. ![Capacity grows, but boosts can expire](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/aehpz9omezpmc0ei8gr0.png) So treat any specific limit you read about, including the ones in this post, as a snapshot. The mechanism is stable. The numbers are not. ## What I actually do Nothing exotic, and it took about a week to settle into. Claude Code stays on the Pro subscription, because that is the best price per unit of work I can get anywhere. I `/clear` between unrelated tasks, which I resisted for a long time and was wrong about. Sonnet is my default and Opus is a deliberate choice, and not a setting I forgot to change. And when something feels slow or expensive, I open `/usage` and read the behavior flags instead of guessing, which is the habit that actually stuck. Underneath the harnesses that aren't Claude Code, my traffic goes through the gateway with a fallback rule, so one provider having a bad day doesn't end my afternoon. I also keep a second subscription with a different vendor, which is less a strategy than an admission that a single meter is a single point of failure. ![A repeatable limit-management workflow](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/kgoac0gdz8fyn2k7yfjy.png) Start with `/usage` this week. Find your biggest behavior flag and fix that before you spend a rupee on a bigger plan. If it says long context, you have a `/clear` habit to build. If it says cache misses, you have a scheduling problem. `/usage` tells you which habit to change first. And if you have found a genuinely clever way to stretch a Pro plan that isn't on this list, I want to hear it in the comments. I write more about LLM infrastructure and building with AI over at [swapnoneel.site](https://www.swapnoneel.site), and I'm on [X (swapnoneel123)](https://x.com/swapnoneel123) if you want to argue about any of this. ## What Is Semantic Caching, and Where It Quietly Breaks URL: https://www.swapnoneel.site/blog/what-is-semantic-caching Date: 2026-08-19T17:39:37.000Z Summary: Semantic caching serves cached LLM answers to queries that only match in meaning. How it works, the threshold trap, real hit rates, and when to skip it. Two people open your support chatbot within the same minute. One types `How do I reset my password?` and the other types `i forgot my password, how do i get a new one`. Same question, same answer, two full model calls, and your Redis cache stores both as separate keys without ever hitting on either. **Semantic caching** stores past LLM responses and serves them to new queries that mean the same thing, even when the words are completely different. It works by turning every query into a vector, searching for the nearest stored vector, and returning that cached answer if the similarity clears a threshold you set. ## Why your existing cache does nothing for LLM traffic Every cache you have ever written works on exact equality. You take the request, hash it, look up the hash, and either the bytes match or they don't. Redis and Memcached both work this way, and so does the HTTP layer sitting in front of them. It works brilliantly. It works because the traffic it was designed for is machine-generated. `GET /api/users/42` is always spelled the same way by the same client, every time. ![Exact matching misses equivalent questions](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3v3qhwmra038m9m4xgsp.png) Human language is not like that. People can ask for a refund policy in many ways, and a hash function treats each wording as a different key. One extra space, one lowercase letter, one "please" at the end, and you get a completely different key. So your hit rate on natural language collapses to nearly zero, and you go on paying for the same answer over and over. Semantic caching fixes the matching function instead of the cache. The storage stays boring. What changes is that you stop asking "are these two strings identical" and start asking "are these two strings close enough in meaning". The difficult part is deciding what counts as **close**. ## How semantic caching works The rule in plain English: *Turn the question into a point in space. Look for the nearest point we have already answered. If it is near enough, reuse that answer.* In practice, it takes five steps: 1. A query comes in. 2. You send it to an embedding model, which returns a vector of floats. 3. You search your vector store for the nearest stored vector, using cosine similarity. 4. If the best match scores above your threshold, you return the stored response and never call the model at all. That's a cache hit. 5. If nothing clears the threshold, you call the model, return the real answer, and write the query vector plus the response into the store with an expiry time. ![The five-step semantic caching pipeline](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/1qpab5iqbnfib7t76sfx.png) In code it is almost insultingly short: ```python def ask(query, threshold=0.92): vec = embed(query) # step 2 match, score = store.nearest(vec) # step 3 if score >= threshold: # step 4 return match.response # cache hit, zero model tokens answer = llm.complete(query) # step 5, cache miss store.put(vec, answer, ttl=3600) return answer ``` Notice what step 4 is really doing. It is taking a floating point number and using it to decide whether a human being gets a fresh answer or a recycled one. There is no other logic in this system, no parsing and no intent classification, nothing else that ever looks at what was actually asked. If you want the mechanics of what `store.nearest` is doing underneath, I built one of these from scratch, cosine similarity and the HNSW graph and all, in my post on [building a vector database from scratch](https://www.swapnoneel.site/blog/build-vector-database-from-scratch). The short version is that it is an approximate nearest neighbour search, so it is fast, and it is also allowed to be a little bit wrong. ## The similarity threshold is the whole product ![One threshold, two opposing failure modes](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/vx141iz8mc6jumndcpk8.png) Consider three pairs of queries and how an embedding model scores them. Pair one. `What is your refund policy?` and `Can I get my money back?` Different words, same intent. This one **should** score high, and it does. Pair two. `Show me the sales numbers for Q1 2025` and `Show me the sales numbers for Q3 2024`. Almost identical strings, completely different answers. You would want this to score low. It does not. It scores extremely high, because most of the tokens are shared and the embedding barely notices which quarter you asked about. Pair three. `Is this drug safe for pregnant patients?` and `Is this drug not safe for pregnant patients?` One word apart, opposite meaning. Embedding models are often poor at handling negation. A [validity audit published in August 2026](https://arxiv.org/html/2608.10216) tested 9 encoder configurations and found that negation and antonym pairs score **above** genuinely similar pairs on every model tested, at average cosines of **0.93 to 0.999**. In the production system that paper audits, flipping an instruction from "withhold the study drug" to "administer the study drug" scored **0.9608**, and the safety gate that existed specifically to catch that never fired. The reversed instruction scored higher than most legitimate paraphrases would. A threshold like 0.92 does not cleanly separate "same question" from "different question". What it often separates is **surface form**, and surface form is not meaning. Two sentences that share a grammatical frame and differ in one date, one entity, or one negation will sit above almost any threshold you are willing to set. That's the trap. Raise the threshold to 0.98 and you kill your hit rate, because honest rephrasings stop matching. Lower it to 0.85 and you start serving Q3 2024's numbers to someone asking about Q1 2025. There is no single number that fixes both, because the failure is in the measurement and not in the cutoff. What helps is refusing to let similarity be the only gate. Partition the cache by anything the embedding is bad at holding: user, tenant, model, and any structured parameter your queries carry. If dates and IDs are pulled out into the cache key instead of being left sitting inside the prose, the embedding never gets a chance to blur them. ## Semantic caching vs prompt caching vs KV caching ![Three caching layers at different depths](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/o2albu3d51p4reqzb2w0.png) These three names get used interchangeably online, but they refer to different layers. **KV caching** lives inside the GPU. During inference the model computes key and value tensors for every token in your context, and the KV cache keeps them around so the next token does not need to recompute attention over everything before it. This is always on, you do not configure it, and it is the reason generation gets faster after the first token. **Prompt caching**, sometimes called prefix caching, is what OpenAI and Anthropic sell you at the API level. It reuses those KV tensors across requests when two requests share a common prefix. So if you send a 4,000 token system prompt on every call, the provider can skip recomputing it and charges you less for those tokens. Important detail: it matches on **exact prefix bytes**, so two prompts that mean the same thing but start differently will miss it entirely. **Semantic caching** sits in front of the model, in infrastructure you control. It stores whole request and response pairs and matches on meaning. When it hits, you save 100% of the call, because the model is never invoked. You can use all three together. A request can try the semantic cache first, fall through to the provider's prompt cache on a miss, and only then pay for full inference. Semantic caching is the outer layer, so it is also the one you control. ## What is a realistic cache hit rate? ![Real-world hits are a minority of requests](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/u8og1qce297e7399xh0h.png) The 95% figure quoted in semantic-caching marketing is easy to misread. You will see 95% quoted constantly. Trace that number back and it almost never refers to hit rate. It refers to **match accuracy**, meaning the cached response was correct 95% of the time it was served. Those are entirely different claims, and the second one tells you nothing about how much money you saved. Actual production numbers are much lower. A [breakdown of real deployment data](https://dev.to/gauravdagde/llm-semantic-caching-the-95-hit-rate-myth-and-what-production-data-actually-shows-8ga) puts typical hit rates at **20 to 45%**, with Portkey seeing around 20% on retrieval-augmented workloads and an EdTech platform hitting about 45% on student question-and-answer traffic. Open-ended chat sits at 10 to 20% because its queries repeat less often. Academic results land in a similar band. The [GPT Semantic Cache paper](https://arxiv.org/pdf/2411.05276) reports cutting API calls by up to **68.8%**, but that is on query categories picked for repetition, which is exactly the workload where this technique looks its best. Even a 20% hit rate can matter. On a $5,000 monthly bill, that is $1,000 saved. A cache hit returns in under 5 milliseconds instead of the 2 to 5 seconds a real completion takes, so the speed difference may matter even more than the cost. Set expectations using your own traffic. ## What semantic caching costs you to run ![Every request pays the semantic lookup toll](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ux3cdjdufpwk8zivmwo1.png) You pay for an embedding call and a vector search on **every request**, including the 60 to 80% that miss. Embeddings are cheap compared to a chat completion, so the money side is fine. The latency is the thing to watch, because you have just added a network round trip to the front of every request in your system, including all the ones the cache cannot help with. At [Keploy](https://keploy.io) I built a retrieval-augmented chatbot over their documentation using vector embeddings, and docs traffic is close to the best case for this technique. People ask the same twenty questions in fifty phrasings, forever. Even there, you trade a small cost on every request for a larger saving on the requests that hit. Measure that ratio before assuming the cache pays off. Then there is staleness. Your cache does not know your prices changed on Tuesday. The stored answer is a frozen snapshot of what the model said, plus whatever context it was given at the time, and it keeps being served until its expiry time runs out. Short expiry times are safer and hit less. Long ones are the opposite. Pick deliberately. Multi-turn conversations are worse. A follow-up like "and what about the second one?" embeds to almost nothing useful, because the meaning lives in the previous four messages and not in that sentence. The safer approach is to stop caching after a few turns of history. And you now operate a vector store. That is one more thing to size, monitor, and pay for. ## One cache, many tenants, and the leak nobody plans for This is the part that turns semantic caching from a performance feature into a security decision, and it is why I would not hand-roll one at the application layer in an enterprise setting. A semantic cache with one global namespace returns the nearest previous response across every user in it. Not the nearest response *belonging to that user*. The nearest one, period. Picture two customers of the same SaaS product asking structurally similar questions about their own account data. Their prompts embed within 0.93 cosine of each other because they are the same question about different companies, and the cache hands one customer the other's cached answer. Nothing errors or logs a violation. It looks like a successful cache hit, making the leak hard to detect. ![A shared cache can cross tenant boundaries](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/bflbiben9y76am58s2yq.png) There is a subtler version too. Even with no wrong response served, cache hits are dramatically faster than misses, and that timing difference is observable from outside. Somebody probing your API can learn which questions have already been asked by other tenants just by watching time to first token. The fix is not clever, it is structural. The cache key has to be partitioned by a tenant identifier resolved from something you trust, meaning the API key, a virtual key, or a signed token claim. Never from the request body, because the request body belongs to the attacker. And the lookup has to be scoped to that namespace so a cross-tenant match is not merely unlikely, it is unreachable. This is why I prefer to handle semantic caching once at the gateway instead of rebuilding tenant isolation inside every service. ## When should you not use semantic caching? ![Some workloads should bypass semantic caching](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/bca2riv6xiz4clpbngq4.png) **Anything that must be current.** Live inventory, account balances, order status. A stale answer here is worse than a slow one. **Anything where the parameters matter more than the phrasing.** Analytical queries over dates, IDs, and entities are precisely where embeddings blur the thing you needed preserved. If you cannot pull those values out into the cache key, skip it. **Anything high-stakes and low-volume.** Medical, legal, financial advice. The negation problem described above can cause serious harm in those domains. And if your volume is low, you were not saving much anyway. **Long open-ended conversations.** A 10 to 20% hit rate while adding latency to 100% of requests is a bad trade. Where it does earn its place: support bots, docs assistants, FAQ layers, onboarding flows, internal knowledge search, and any product where a large user base asks a small set of questions in a lot of different ways. ## How Bifrost does semantic caching at the gateway [Bifrost](https://docs.getbifrost.ai/overview) is Maxim AI's high-performance, [open-source AI gateway](https://github.com/maximhq/bifrost) that unifies access to 20+ providers through a single OpenAI-compatible API. Because semantic caching runs at the gateway, one cache policy is applied before requests reach any configured model provider. Its [semantic cache](https://docs.getbifrost.ai/features/semantic-caching) combines exact hash matching with vector similarity search, supports per-request TTL and threshold overrides, and keeps cache entries separate by model and provider by default. Bifrost uses two cache layers. Every request first goes through a direct hash lookup. If the prompt is byte-identical to something already cached, it returns immediately with zero embedding overhead, which matters because you just skipped the round trip that would otherwise tax every request in the system. Only on a direct miss does it embed the query and run the similarity search. So the cheap path stays cheap, and the expensive path only runs when it might actually pay off. ![Direct and semantic cache layers at the gateway](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/j1gnm7fit9fqcewhgu4m.png) **Caching only activates when a request carries a cache key**, passed as an `x-bf-cache-key` header. There is no global-namespace mode for you to accidentally ship. If you want per-tenant isolation, the tenant identifier goes in that header, and cross-tenant matches then cannot happen because those entries are not in the same partition. Requiring a cache key makes isolation mandatory rather than optional. Its other defaults are: - `threshold` defaults to **0.8** for semantic hits, overridable per request with `x-bf-cache-threshold`. - `ttl`, the time to live on an entry, defaults to **5 minutes**. This reduces stale answers, and you can override it per request. - `conversation_history_threshold` defaults to **3**, which means it stops caching once a conversation runs past 3 messages. - `cache_by_model` and `cache_by_provider` are both on by default, so a cached GPT answer never gets served to a Claude request. - The vector store is pluggable across Redis or Valkey, Weaviate, Qdrant, and Pinecone, so you are not forced into adopting a new database. Every response carries a `cache_debug` block with `cache_hit`, `hit_type` (direct or semantic), the actual `similarity` score, and a `cache_id`. The similarity score helps diagnose a bad match, while `cache_id` lets you remove the affected entry or clear its partition through the API. Every cache hit avoids the completion cost. At the gateway, the cache sits next to budgets, virtual keys, and routing. The same layer that chooses *which* provider gets a request can first decide whether the request needs a provider at all. I cover the routing side separately in [what adaptive load balancing actually is](https://www.swapnoneel.site/blog/what-is-adaptive-load-balancing). Bifrost adds under 100 microseconds of overhead at 5,000 requests per second, which is negligible next to the embedding call. ## What should you do first? Do not start by building the cache. Start by measuring how repetitive your traffic is. Take a week of your logs, embed the queries, and count how many land within 0.92 of an earlier one. That gives you an upper bound on the possible hit rate. A week of your own traffic will tell you more than a benchmark based on someone else's workload. If that number comes back at 30% or better, turn semantic caching on at your gateway. Partition the cache key by tenant from day one and keep the expiry short. For the first few weeks, watch the similarity scores on cache hits instead of trusting the threshold. If it comes back at 8%, the workload is too varied for semantic caching to save much. ![Measure repetition before turning caching on](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/wpivlvi3ri9qiqp79l0k.png) If you have run a semantic cache in production and watched it serve something it should not have, tell me about it in the comments. Those failures are the useful stories. ## AI Observability Explained: What It Is and How It Works URL: https://www.swapnoneel.site/blog/ai-observability-explained Date: 2026-08-18T06:25:25.000Z Summary: AI observability is how you see inside a non-deterministic system. What to trace on every call, why logs are not enough, and where it belongs. Traditional monitoring rests on one quiet assumption that nobody ever writes down: the same input gives you the same output. Something breaks, you replay the request, you watch it break again, you fix it. Now send the same request to a model twice. You get two different answers, and neither one of them threw an error. **AI observability** is the practice of recording what happened inside an AI system on every request: the prompt, the model version, tokens, cost, latency, tool calls, and a judgement of whether the output was any good. Monitoring tells you the service is up. Observability tells you why it answered that way. That gap is the whole story here. ## Why your current monitoring stack misses all of this Your existing setup is watching for crashes. Status codes, error rates, p99 latency, memory. All of it is designed around the idea that a broken thing looks broken. An AI feature failing looks nothing like that. It returns HTTP 200 in 900ms, with grammatically perfect prose that happens to be wrong, or that quietly ignored the document you retrieved for it, or that called the refund tool when the user only asked a question. Your dashboard sees a healthy service, because by every measure it has, the service is healthy. And there are whole categories of failure your stack has no field for. It has nowhere to put "this response cost 14 cents", or "the model version changed under us last Tuesday", or "the retrieved context was garbage". Those are not infrastructure facts, and standard telemetry was never built to carry them. ![Why standard monitoring misses AI quality failures](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/72i1wisqwyz7zwvurepu.png) Something has to hold those fields instead, which is the entire reason this tooling exists. [Bifrost](https://docs.getbifrost.ai/overview) is Maxim AI's high-performance, [open-source AI gateway](https://github.com/maximhq/bifrost) that unifies access to 20+ providers through a single OpenAI-compatible API. Its position between the application and the model providers makes it useful for observability: every model request and response passes through the same layer, regardless of provider. Bifrost's [built-in observability](https://docs.getbifrost.ai/features/observability/default) records the input, output, provider, model, token usage, cost, latency, errors, and status for each call, then makes those fields searchable in a live dashboard. Those are the details standard HTTP telemetry misses even when the request returns `200`. ## What one AI request actually looks like when you trace it This is the part that made it click for me, so let me walk through a real shape. At [Keploy](https://keploy.io) I built a retrieval-augmented chatbot over their documentation, using vector embeddings, so developers could ask a question instead of hunting through pages. A single question to something like that is not one operation. It is a chain, and a trace is just that chain written down. One request breaks into spans, where a **span** is one step with its own start time, end time, inputs and outputs: 1. The user's question comes in and opens the root span. 2. The question gets embedded into a vector. That is a span, with its own model and its own cost. 3. The vector search runs and returns, say, five chunks of documentation. That is a span, and the important bit is that it records _which_ five chunks came back. 4. Those chunks get stuffed into a prompt template along with the chat history. 5. The model call goes out. This span carries the model name and version, the temperature, the prompt tokens, the completion tokens, the cost in dollars, the total latency, and the time to first token. 6. If the model calls a tool, every one of those is its own child span too. Now here is why anybody bothers with all that plumbing. When the bot gives a bad answer, you do not have to guess. You open the trace and look at step 3. If the vector search pulled back five irrelevant chunks, your problem is chunking or embeddings, and the model did nothing wrong. If the search pulled back exactly the right documentation and the model still answered from thin air, your problem is the prompt. Two completely different fixes, and without the trace you cannot tell them apart. All you have is "the bot said something dumb", which is the single most useless bug report in the world. ## The things worth capturing on every call You will notice I have not called this section "the three pillars of observability". Everyone else writing about this does, and I dropped it on purpose, because logs, metrics and traces is a framing built for deterministic systems and it has no slot at all for "was the answer any good". ![The telemetry worth capturing on every AI call](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3s0utdi83ubyijnvze1x.png) So here is the actual list: - **The full prompt and the response**, as they really went over the wire, after every template and system message got assembled. Not the template, the final text. - **Model, version and parameters.** Providers ship silent updates. If you cannot say which exact version answered a request, you cannot explain last month's regression. - **Tokens in, tokens out, and cost in dollars** per request, attributed to a user or a feature. - **Latency, split into total time and time to first token.** Those two numbers feel completely different to a user, and one can get worse while the other improves. - **Tool calls, retries and fallbacks.** Which key was tried, what failed, what it fell back to. - **A trace ID that ties the whole chain together**, and ideally a session or user identifier so you can reconstruct a full conversation. - **A quality score**, attached after the fact. More on that next. I built one of these myself earlier this year, an internal tool at a contract role that captured an AI product's logs and turned them into reports on latency and probable slowdowns (keeping it vague on purpose, cannot say much more than that). The honest takeaway was not that the tool was clever. It was that a team can ship for months on vibes, and the moment somebody puts the per-request numbers on a screen, problems nobody previously had words for suddenly have words. ## How do you measure quality when there is no right answer? Well, you do not measure it the way you measure a unit test, because there is no expected string to compare against. The industry has mostly settled on three overlapping things. **LLM-as-judge**, where you send the input and output to a second model with a rubric and it scores relevance or faithfulness or tone. It is imperfect, and it is far better than nothing. **Human annotation** on a sample, which is slow, expensive, and still the ground truth everything else gets calibrated against. And **implicit user signals**, like thumbs, edits and retries, which are noisy but free. Run those continuously and you get **drift detection**, which is just the same score measured over time. When your faithfulness score drops 8 points over two weeks and nobody deployed anything, something moved underneath you, and that is usually the model provider. ![Three ways to measure AI output quality](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/rhkiusbk7ev7rfgmtj6c.png) This is the half most teams skip, and there are numbers on it. In LangChain's [2026 State of Agent Engineering report](https://www.langchain.com/state-of-agent-engineering), which surveyed 1,300+ practitioners, 89% said they had observability running on their agents while only 52% were running evaluations. So most people are recording what happened and still have no systematic opinion on whether it was good. Which is also why I keep saying you cannot test an AI feature the way you test code. I went into that failure mode properly in my post on [testing AI coding agents](https://www.swapnoneel.site/blog/testing-ai-coding-agents). ## Where AI observability actually lives in your stack Two choices here, and you can do both. You can instrument your application directly, wrapping every model call in your own code. That gives you the most context, because your code knows what the user was doing. It also means every service, every language and every framework has to be instrumented separately, and someone has to keep it consistent. Or you put it in the gateway. If all your model traffic already goes through one proxy, that proxy sees every request and every response by definition, and you get telemetry for services you never touched. And the reason you can do both without doubling the work is that there is finally a shared standard. The [OpenTelemetry GenAI semantic conventions](https://opentelemetry.io/blog/2026/genai-observability/) define agreed attribute names for exactly this, like `gen_ai.request.model`, `gen_ai.usage.prompt_tokens` and `gen_ai.usage.cost`. Emit those and your AI spans slot into the same traces as the rest of your system, in whatever backend you already pay for. Bifrost is a reasonable thing to look at here, since it does both halves. It records inputs, outputs, tokens, cost and status for every call into SQLite or Postgres with a dashboard on top, and it exports OpenTelemetry spans using those GenAI conventions plus native Prometheus counters like `bifrost_input_tokens_total` and `bifrost_cost_total`. The logging runs in background goroutines, which is why [its documentation](https://docs.getbifrost.ai/features/observability) puts the added overhead under 0.1ms per request. That last detail is the pattern to steal, whichever tool you end up picking. Telemetry gets emitted off the hot path, after the response is already on its way back to the user. Observability that slows down the thing it observes gets switched off within a week. The routing side of that same gateway is worth knowing about too, and I covered it in my post on [adaptive load balancing](https://www.swapnoneel.site/blog/what-is-adaptive-load-balancing). ## What it costs you to run Now the uncomfortable part, because none of this is free. ![The storage, privacy, and attention costs of observability](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/78xs5rtn8pt5huvu3cyl.png) **Storage adds up fast.** You are storing full prompts and full responses, and prompts got long. A retrieval app can easily push 8,000 tokens of context per call. At real traffic that is a serious volume of text, and this is where sampling comes in: keep 100% of errors and slow requests, keep a small percentage of the healthy ones. **Your prompts contain user data.** Every support chat, every uploaded document, every email a user pasted in. The moment you log all of it, your observability store is now a system holding personal data, with all the retention and access rules that implies. Redact at the point of capture, not later. **And someone has to actually look at it.** This is the one that quietly kills the whole effort. The traces get collected, the dashboard gets built, nobody opens it, and six months later it is a very expensive write-only database. ## Frequently asked questions **Is AI observability the same as LLM monitoring?** Close, and monitoring is the narrower one. Monitoring tracks known metrics like uptime, latency and error rate, and answers "is it working". Observability keeps enough per-request detail that you can answer questions you had not thought of yet, like "why did this one user get that answer". In practice most tools sell both under one name. **Do I need OpenTelemetry for this?** No, but it is the sensible default in 2026. The GenAI semantic conventions mean your AI spans use the same attribute names everywhere, so you can change vendors without reinstrumenting, and your model calls appear inside the same traces as your database queries. Note that parts of the spec are still marked experimental, so pin your versions. **What is the difference between observability and evals?** Evals are the measurement, observability is the pipe. Evals score whether an output was good; observability captures the request, the context, the cost and the trace so the score has something to attach to. You can run evals offline in CI against a fixed dataset, but you can only run them on real traffic if the traffic is being recorded. ## So what should you actually do? If you have an AI feature in production right now and you cannot pull up the exact prompt, the model version and the cost of a request from last Tuesday, that is the gap, and it is worth a day of your week. Start with capture, and not with dashboards. Get every request logged with its prompt, response, model, tokens, cost and a trace ID, put it wherever you already look at data, and give it two weeks. You will find something. Everyone does. Quality scoring, drift alerts and per-feature cost budgets are worth adding later, but every one of them sits on top of the boring capture layer, so there is no point doing them first. If your model calls already go through a gateway, turn on the telemetry it ships with before you write any of this yourself. That is the single highest-value hour available to you here, and it is mostly a config change. ![Capture first, then add quality, drift, and cost controls](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/0jqt2fv5869zcc74abon.png) That is my read on it, and your setup might look nothing like mine. If you have built this kind of tracing yourself, or you have had an AI observability bill genuinely surprise you, drop it in the comments, I would like to hear how it went. You can find me on [X](https://x.com/swapnoneel123) where I post about most of what I am building, and the rest of my writing lives at [swapnoneel.site](https://www.swapnoneel.site). ## What is Adaptive Load Balancing, and Why AI Needs It URL: https://www.swapnoneel.site/blog/what-is-adaptive-load-balancing Date: 2026-08-17T00:00:00.000Z Summary: Adaptive load balancing routes by live health, not a fixed rotation. How the scoring actually works, and why AI traffic breaks the older algorithms. Five identical servers sitting behind one load balancer, each getting exactly one-fifth of the requests. So why is one of them pinned at 90% CPU while another one sits half idle? Well, because an equal share of requests is not an equal share of work. **Adaptive load balancing** is a routing strategy that picks a destination using live health signals like error rate, latency and utilization, instead of a fixed rotation. The balancer keeps scoring every backend while traffic flows, shifts weight toward the ones behaving well, and pulls weight away from the ones going bad. ## What is a load balancer? A load balancer is just a thing sitting in front of your servers, deciding which one gets the next request. That's it. The simplest version is round robin. Request 1 goes to server A, request 2 to server B, request 3 to server C, then back to A again. It's a rotation, and it never once looks at what is actually happening inside those servers. Static algorithms like this quietly assume two things: every request costs the same, and every server has the same capacity right now. Both assumptions survive about five minutes of real production traffic. ![Equal turns can still create unequal work](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/bltwhnlm4r1ooiqa9pvt.png) Think of a supermarket. Round robin is the sign saying "next customer to the next till, in order." Adaptive is a floor manager who watches which till is genuinely moving, spots the one stuck behind a price check, and sends people elsewhere. Same queue, very different Saturday. ## How does an adaptive load balancer decide where to send a request? It collects signals, turns them into a score per route, and turns those scores into weights. Three signals do most of the work: 1. **Error rate.** Is this backend returning failures? Usually the heaviest signal, and usually time-decayed, so a spike from ten minutes ago stops dominating the decision. 2. **Latency.** How slow is it right now, both against its peers and against its own recent baseline? A route that always takes 2 seconds is fine. A route that usually takes 200ms and is now taking 2 seconds is in trouble. 3. **Utilization.** How much of its capacity is already committed, so that no single fast route gets hammered into becoming a slow one. Those collapse into one number per route, and a higher number means a bigger share of the traffic. And the weights are not recalculated per request, because that would drop real work onto the hot path. They get recalculated on a background loop, and each incoming request simply reads the numbers that were computed a moment ago. ![Live signals become route weights](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/tpeg1tbewphirbc8s1pa.png) [Bifrost](https://docs.getbifrost.ai/overview) is Maxim AI's high-performance, [open-source AI gateway](https://github.com/maximhq/bifrost) that unifies access to 20+ providers through a single OpenAI-compatible API. That makes it a useful case study here: the gateway sees provider, model, and API-key routes from one layer, which is the full decision surface its adaptive balancer needs. The [documented implementation](https://docs.getbifrost.ai/enterprise/adaptive-load-balancing) scores routes on error penalty, token-aware latency, utilization, and recovery momentum. It recalculates weights every 5 seconds, monitors both provider and key performance, and keeps route selection under 10 microseconds on the hot path. That provider-and-key view matters because a provider can remain healthy while one API key is throttled or exhausted. A balancer that only watches provider health would miss that failure and keep sending traffic to a route that cannot serve it. The other half of the mechanism is state. A good adaptive balancer doesn't just have a dial, it has an opinion about what each route currently is: healthy, degraded, failed, or recovering. And the recovering state is the one people forget. A route that failed does not get cut off forever, it gets a thin trickle of live traffic so the balancer can find out when it is better. Without that, your balancer is just a fancy circuit breaker that never closes again. ## Adaptive load balancing vs round robin and least connections Each static strategy fails in a different way. **Round robin** rotates blindly. Fine when every server and every request is genuinely identical, which is basically never. **Weighted round robin** lets you say "server A is beefier, give it double." Better, but you set those weights by hand, based on what was true when you deployed. It has no idea what is true at 3am during a traffic spike. **Least connections** picks whichever server has the fewest open connections. This one is genuinely dynamic, and it is a solid default. But an open connection is a rough proxy for load, since one connection doing heavy work counts exactly the same as one connection idling. **Least response time**, usually implemented with an exponentially weighted moving average of latency, gets close to adaptive. It measures the thing you actually care about. Adaptive load balancing is the version that stops relying on any single number. It combines errors, latency and capacity, keeps a health state per route, and has explicit behavior for pulling a route out and easing it back in. Does the extra machinery pay off? Envoy's own benchmark for its Peak EWMA policy puts it at a 99.9% success rate under a 1-second timeout, against 99% for least-loaded and 95% for round robin ([Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/contrib/load_balancing_policies/peak_ewma/peak_ewma)). That gap between adaptive and round robin is made entirely of user-visible failures. ## Why an old routing idea matters again Adaptive load balancing has been used in networking gear and reverse proxies for years. For a normal web app behind NGINX, least connections is often enough. AI traffic changes that because it breaks the assumptions behind the older algorithms. ## Why LLM traffic makes static load balancing fall apart? ![Large token loads expose static routing limits](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/rlgf0fpspsc35cdk60qi.png) Four things go wrong at once. **Requests are wildly different sizes.** A "summarize this sentence" call and a "read these 40 pages and reason about them" call hit the same endpoint, and one of them costs a hundred times more. When I built [Scholarian](https://scholarian.vercel.app), a deep-research pipeline over academic papers, it ended up fetching and ranking over 10,000 papers across 250+ search sessions. Some sessions were one cheap query. Some were a long chain of very expensive ones. A rotation cannot tell those apart, so it cheerfully fires the expensive one at the route that is already drowning. **The limits are not counted in requests.** Model providers rate-limit you on requests per minute _and_ tokens per minute, and it is usually the token ceiling you hit first. So a balancer counting requests is watching the wrong meter, and you find out about it through a 429 error in production. **The backends are not yours.** You cannot SSH into OpenAI. There is no CPU graph, no memory reading, nothing except the latency and error rate you observe from outside. Observed behavior is the only signal you have, and observed behavior is exactly what adaptive balancing runs on. **Every API key is its own bottleneck.** Rate limits are per key, so teams end up holding several keys per provider. Now you are not balancing across servers anymore, you are balancing across a grid of providers and keys, each with separate limits and separate health. ## What adaptive load balancing looks like inside an AI gateway? ![Provider selection followed by API key selection](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/c8hkbqxrhshh2kazha4j.png) The gateways handling this properly split the decision in two. The first level picks the provider and model for a request, based on live capacity and error rates across all of them. The second level picks which API key inside that provider actually gets used, weighted by how each individual key is performing. That two-level shape matters because the failures are different at each level. A provider goes down for everybody at once. A single key just quietly hits its own token ceiling while its siblings are perfectly fine. One balancer trying to handle both would be making the wrong call half the time. I went through the practical side of this in my post on [Bifrost's enterprise features](https://www.swapnoneel.site/blog/bifrost-for-enterprises), where the adaptive routing sits right next to audit logs and guardrails. Worth a read if you want the version with an actual dashboard in front of you. ## When you should not reach for adaptive load balancing ![Sparse signals and weak capacity call for caution](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3e6j3ogmivqjmcrf5vl8.png) **You need real telemetry before you need adaptive routing.** At a contract role earlier this year I built an internal tool that captured our AI product's logs and generated reports on latency and probable slowdowns, and the uncomfortable lesson was that most of the wins came from simply _seeing_ the numbers. Half the routing problems people want an adaptive balancer to solve turn out to be one bad prompt template or one undersized instance, and a dashboard finds those faster than an algorithm hides them. **Low traffic means no signal.** Scoring on error rate and latency needs enough requests per window to mean anything. At 5 requests a minute, an adaptive balancer is mostly reacting to noise, and reacting to noise is worse than not reacting at all. **It can paper over a capacity problem.** If every route is degraded, adaptive balancing will smoothly and confidently send you to the least-bad option, forever, while the real answer was "add capacity" or "get off the free tier." **And it is one more moving part.** More state, more tuning, one more thing to reason about at 3am. If round robin across two identical boxes is working for you, keep it. ## Choosing the right routing strategy If you run a plain web service on infrastructure you control, least connections is fine and adaptive load balancing is over-engineering. If you are routing to model providers, it is not optional anymore. You are balancing across backends you cannot inspect, with limits measured in tokens, with per-key ceilings, and with failure modes that arrive as a slow degradation instead of a clean crash. A fixed rotation has no mechanism to even notice that. Use a gateway that scores routes on live behavior and moves the traffic for you, and go spend that attention on your product instead. And also, if what Bifrost is doing intrigued you, you can easily [book a demo](https://www.getmaxim.ai/bifrost/book-a-demo), and see how it fits in your organization. ![Simple services can stay simple; AI traffic needs adaptation](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/tjxt9en3l4yzf1gelqlr.png) That is my take, and your setup might look nothing like mine. If you have built this kind of routing yourself, or you have watched an adaptive balancer make a genuinely stupid decision, drop it in the comments, I want to hear it. You can find me on [X](https://x.com/swapnoneel123) where I post about most of what I am building, and the rest of my writing lives at [swapnoneel.site](https://www.swapnoneel.site). ## Bifrost for Enterprises: Audit Logs, MCP Tool Groups, Adaptive Routing, and Guardrails URL: https://www.swapnoneel.site/blog/bifrost-for-enterprises Date: 2026-08-09T15:41:05.000Z Summary: Bifrost Enterprise builds on Maxim AI's open-source gateway with audit logs, MCP tool groups, adaptive routing, and guardrails. This is a hands-on review of those features. [Bifrost](https://docs.getbifrost.ai/overview) is Maxim AI's high-performance, [open-source AI gateway](https://github.com/maximhq/bifrost) that unifies access to 20+ providers through a single OpenAI-compatible API. [Bifrost Enterprise](https://docs.getbifrost.ai/enterprise/overview) is a strict superset of that gateway, adding high-availability clustering, adaptive load balancing, identity and access controls, audit logs, guardrails, and private deployment options. Those additions matter when several teams share the gateway. Routing rules, access policies, MCP tool permissions, and administrative changes can be managed and audited in one layer instead of being implemented separately in every AI application. After writing about the open-source gateway in my previous two posts, I asked the Bifrost team for access to the Enterprise version. They gave me limited access to the paid features for free. This article covers what I tested and whether those features make sense for personal and enterprise use. ## Transparency through Audit Logs So, before creating any new Enterprise configuration, I wanted to see whether Bifrost could actually tell me what was happening behind the scenes. Audit Logs tell you exactly that. It’s different from the regular LLM logs because they show the requests going through the gateway, while Audit Logs focus on changes and administrative activity inside Bifrost. So if someone creates a virtual key, changes a routing rule, updates a guardrail, or modifies the cluster configuration, this is where we should be able to find it. ![Bifrost dashboard showing the Audit Logs interface.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/cgnv1ejfcvkgmrohejyj.png) For a personal setup, audit logs may be more detail than you need. In a company, they give security and platform teams a record of who changed shared routing, access, and guardrail configuration. ## What are MCP Tool Groups? In my [previous blog](https://www.swapnoneel.site/blog/deep-dive-into-bifrost), I already mentioned how I connected the MCP Gateway to OpenCode using the Virtual Key, which enabled Bifrost to expose all of my configured MCP tools through that one endpoint. But that also raised a question: do I really want every harness to have access to every tool? So, this time I decided to try MCP Tool Groups. The idea is pretty simple. We can create a group of selected MCP tools and attach that group to a virtual key. OpenCode already uses my dedicated Enterprise virtual key, so I can control the tools available to it without changing the rest of my MCP setup. In the previous blog, you might have seen that I’ve used the Context7 MCP server. It provided access to two tools: - `resolve-library-id` - `query-docs` So, while creating the tool group, I decided to drop the `query-docs` and kept only the `resolve-library-id` activated. ![Bifrost dashboard showing the 'Edit Tool Group' panel](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/re84bfyifr07hwij3hl5.png) Then, under Associations, I attached the group only to my opencode-enterprise virtual key. I didn't attach it to any teams, customers, providers, or other keys. The OpenCode MCP configuration itself didn't need much change. It was already pointing to Bifrost's remote MCP endpoint: ```json { "mcp": { "bifrost": { "type": "remote", "url": "https://bifrost-enterprise.agitracker.io/mcp", "enabled": true, "oauth": false, "headers": { "Authorization": "Bearer {file:./bifrost-virtual-key}" } } } } ``` I kept the virtual key inside a separate local file, so it never had to be pasted into the configuration or committed to Git. After restarting OpenCode, I used a deliberately small prompt, `Use the Context7 MCP to resolve the React library. Reply with only the returned library ID.` ![Bifrost MCP logs dashboard showing metrics](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/fncld2oo6svgypq11g2t.png) The request executed successfully, as you can see in the above screenshot. So, is this feature useful? The idea is excellent, especially when different coding harnesses should have access to different MCP tools. And, it gives you a central place to manage tool access instead of duplicating MCP configuration across every client. ## What is Adaptive routing? The next feature I wanted to try was Adaptive Routing. The idea behind it is quite useful, especially if you are running several models, providers, or API keys through the same Bifrost gateway. Normally, requests are distributed using fixed weights. For example, if two Gemini keys have the same weight, Bifrost can send roughly half of the traffic to each one. The problem is that fixed weights do not know whether one key has become slower, started returning errors, or hit a rate limit. Adaptive Routing tries to solve that automatically. Bifrost monitors the latency, error rate, success rate, and utilization of each available route. It then recalculates their weights every few seconds. And, a healthy and faster route receives more traffic, while a failing or slow route receives less. Bifrost still sends a small amount of traffic to recovering routes so it can detect when they become healthy again. The routing happens at two levels: 1. Bifrost can select which provider should handle a model request. 2. After selecting the provider, it can choose the best API key configured for that provider. This makes the feature more useful for companies that maintain multiple provider accounts or keys. Instead of manually changing weights whenever a provider starts acting up, Bifrost can react to the recent performance data on its own. ## How to set Guardrails? Provider safety controls do not cover every organization's policies. Putting custom guardrails at the gateway lets the same input and output checks run across every model behind it. This feature is meant to protect both the prompts sent to a model and the responses coming back from it. Bifrost separates the feature into two parts: rules decide when a check should run, while profiles define what kind of check should be performed. For this test, I wanted to avoid adding another external API key, so I chose Bifrost's built-in Custom Regex provider. According to the [Guardrails documentation](https://docs.getbifrost.ai/enterprise/guardrails), Custom Regex runs locally and can be used for deterministic pattern checks. So, I created two guardrail rules. One for the input, and the other one for the output. ![Bifrost dashboard showing the 'Edit Guardrail Rule' sidebar interface](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/5hko4nft3m5do9q2uxxm.png) So, now if I send a request like `Reply with exactly: BIFROST_GUARDRAIL_TEST`, I get a `regex pattern matched` error. That’s where the guardrail is actually doing its job. The regex rule blocked the matching output without calling an external guardrail service. ## Final thoughts When I started exploring Bifrost Enterprise, I expected the paid version to feel like the open-source gateway with a few extra switches. That wasn’t true. The core experience stayed familiar, and I could continue using OpenCode through one Bifrost endpoint while the gateway handled the provider connection underneath. That part was convenient. I did not need to change my workflow every time I switched between OpenAI and Gemini. The enterprise features that made the most sense to me were Audit Logs and MCP Tool Groups. Audit Logs give teams a central record of what happened, while MCP Tool Groups make it easier to control which tools a client can access. Would I use Bifrost Enterprise for my personal setup? Probably not if I only had one provider, one API key, and a handful of requests. The extra governance and operational features would be more machinery than I need. For a team running several models, provider keys, MCP clients, and internal users, the situation is different. A shared gateway, centralized logs, access controls, guardrails, and tool restrictions can remove a lot of repeated setup from individual applications. And if you think you are the right candidate, you can always [book a demo](https://www.getmaxim.ai/bifrost/book-a-demo)! ![Please like, follow and share!](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/8caibv8q641pm9okuo0d.png) ## Deep Diving Into Bifrost: Virtual Keys, MCP and Skills URL: https://www.swapnoneel.site/blog/deep-dive-into-bifrost Date: 2026-07-31T00:00:00.000Z Summary: A hands-on look at Bifrost, Maxim AI's open-source gateway for 20+ AI providers, covering virtual keys, MCP, prompts, skills, and custom log headers. [Bifrost](https://docs.getbifrost.ai/overview) is Maxim AI's high-performance, [open-source AI gateway](https://github.com/maximhq/bifrost) that unifies access to 20+ providers through a single OpenAI-compatible API. The features in this post all use that shared gateway layer. [Virtual keys](https://docs.getbifrost.ai/features/governance/virtual-keys) can restrict providers and models, enforce budgets and rate limits, and control which MCP tools a client can use. The [MCP gateway](https://docs.getbifrost.ai/mcp/overview) connects to external tool servers and exposes the permitted tools through one endpoint, so each coding harness can have a different policy without carrying a separate copy of every provider and MCP configuration. In my previous blog, I explained how I started using Bifrost to avoid switching provider connections whenever I hit a rate limit. You can read that setup in [Trying Bifrost: An AI Gateway That Simplified My Setup](https://www.swapnoneel.site/blog/trying-bifrost-ai-gateway). Since then, I have been daily driving [Bifrost](https://github.com/maximhq/bifrost/) with multiple coding harnesses including OpenCode, jcode and Pi, to name a few. Initially, I was perfectly fine with just the fallback mechanism and complexity routing. But every time I skimmed through the dashboard, the other features kept intriguing me. I wanted to find out whether those features solved problems beyond the rate-limit setup I already had. ## Setting Limits With Virtual Keys So, you can create a virtual API key and set custom limits on how many tokens and requests it can allow in a set time period. Setting it up is pretty easy through the dashboard. ![The Bifrost virtual keys dashboard, creating a key named opencode-local with Gemini and OpenCode Zen attached to it](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/a9fhtat7d34dmp2h8mov.png) Just give it a name and an expiry duration, then add the providers and the models you want to allow through this key. And done! You can take this key and set it up in your desired harness, similar to the way we discussed [in the earlier blog](https://www.swapnoneel.site/blog/trying-bifrost-ai-gateway). But this time, we should make a separate file for the key, and keep it in the same directory as our opencode config file. I've named mine `bifrost-virtual-key`, and it holds nothing but the key itself. OpenCode can read a value straight out of a file using the `{file:...}` syntax, and a relative path there resolves against the config file's own directory, not wherever you happen to launch the terminal from. So the key never has to sit inside the config, and never has to go into git. After that, pasting this in our config: ```json { "model": "bifrost-local/gemini/gemini-2.5-flash", "small_model": "bifrost-local/gemini/gemini-2.5-flash", "provider": { "bifrost-local": { "npm": "@ai-sdk/openai-compatible", "name": "Bifrost Local", "options": { "baseURL": "http://localhost:8080/v1", "apiKey": "{file:./bifrost-virtual-key}" }, "models": { "gemini/gemini-2.5-flash": { "name": "Gemini 2.5 Flash via Bifrost" }, "opencode-zen/big-pickle": { "name": "Big Pickle via Bifrost" } } } } } ``` The `small_model` field there is worth a word, since it's easy to skip past. OpenCode uses it for cheap background work like generating session titles, and if you don't set it, it goes hunting for a cheaper model on its own. Pointing it at the same model keeps everything flowing through one virtual key, which is the whole point of the exercise. This feature is pretty useful when you are experimenting with different models, and for long-horizon tasks, where you don't want the model to run indefinitely and get stuck in a loop, just burning expensive tokens. Or whenever you want fine control over the MCPs, tools or skills that your harness can get access to. So let's set a deliberately tiny limit and watch it bite. ![Rate limiting configuration for the virtual key, set to a maximum of 1000 tokens and 10 requests, both resetting hourly](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/n1uzzssp5q6qmi5v1vhn.png) As you can see, I've set a maximum of 1000 tokens and a maximum of 10 requests, both resetting every hour. That token budget is small on purpose, because I want to hit the wall quickly rather than wait around for it. I've already connected this key to my OpenCode setup, so a couple of ordinary messages should be enough to get me blocked. ![Bifrost logs showing two successful requests that burned 13.57K tokens, followed by ten failed requests once the token limit was crossed](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/6t4h091k4dsmanep2cfy.png) And that's exactly what happened, though the way it happened is the interesting part. My first message cost 627 tokens and went through fine. The second one went through too, and quietly cost 12.94K tokens by itself. Every single request after that got rejected, which is why the success rate sits at 16.67%, only 2 requests out of 12. So the limit isn't checked against the size of the request you're about to make, it's checked against what you have already spent. A request gets waved through as long as you're under budget at that moment, and it can then blow straight past the ceiling on its own. Worth knowing before you set a budget you actually care about, because the cap decides when the blocking starts, and not how much you can spend in total. One more thing to notice in that screenshot: each failed attempt shows up twice, once against gemini and once against big-pickle. That's the fallback rule from the last blog doing its job. Bifrost tried the fallback, and the fallback got refused by the same virtual key, which is exactly what you'd want. ## The MCP Gateway This is the one that I regret not trying earlier. Using the MCP gateway we can connect all our MCPs in one place, and then selectively allow access to our harnesses using the virtual key. So instead of every harness carrying its own copy of every MCP config, Bifrost holds them all, and each key gets to see only the slice you've allowed it. There are a huge number of MCPs already present in the [MCP Server library](https://www.getmaxim.ai/bifrost/mcp-servers), 487 of them at the time I'm writing this, and it's just a one-click installation from there. As you can see in the screenshot below, I have already installed the Context7 MCP. And now, I will just add this to my virtual key from the MCP Client Configuration option, and we are good to go. ![The Bifrost MCP Server Library showing 487 available servers, with Context7 marked as installed](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/52yz7mhdo4guxv5lcs42.png) As we have already added our virtual key to our OpenCode harness, we can now easily use the Context7 MCP from there. Bifrost exposes every MCP you've installed at a single endpoint, `/mcp`, and the virtual key you send as a Bearer token decides which tools come back. So from OpenCode's side, this looks like one ordinary remote MCP server, no matter how many you have installed behind it. Here's the block to add to the config file: ```json { "mcp": { "bifrost": { "type": "remote", "url": "http://localhost:8080/mcp", "enabled": true, "oauth": false, "headers": { "Authorization": "Bearer {file:./bifrost-virtual-key}" } } } } ``` The `oauth: false` line matters more than it looks. OpenCode will try to start an OAuth flow on its own when a remote MCP server answers with a 401, so you have to tell it not to bother here, because we are authenticating with a fixed key instead. Let's test it by asking OpenCode something that can only be answered by fetching live documentation, and then check whether the tool calls actually show up on the Bifrost side. ![Bifrost MCP logs showing two successful Context7 tool executions, resolve-library-id and query-docs](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/z86aq2lb1z6jcc9xygxu.png) And there they are. Two tool calls, `resolve-library-id` followed by `query-docs`, both against the Context7 server, both successful. So the MCP is working perfectly as intended, and I never had to put a Context7 config into OpenCode at all. ## The Prompt and Skills Repositories I used the Prompt Repository to test the same prompt against different models and revise it before turning it into a skill. And as you can see, I've created a code review prompt that accepts the language, the review focus and the code snippet, and gives you a detailed review of it, along with the revised code if your code needs any fixing. ![The Bifrost prompt playground running a code review prompt, with code, language and review_focus variables filled in on the right](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/6yl65270vb753sc5r70d.png) The variables are the neat bit here. Anything you write as `{{ code }}` or `{{ language }}` inside the prompt gets picked up automatically and turned into a field you can fill in, so you're changing the inputs and not rewriting the prompt every time you want to try something. And prompts here are versioned, which I didn't expect. You keep editing in a session, and when something is actually good you commit it as a version, so the thing your application calls later is a version you deliberately shipped, and not whatever you happened to be typing five minutes ago. Now once you are done testing your prompts, you can create a skill out of that as well, and add it in the skills repository. I've created this skill, `safe-bug-fix`, that can be used to fix bugs in a codebase. It's a plain SKILL.md, with a "when to use" section, a numbered workflow and a set of safety rules, and it's targeted at OpenCode. ![The safe-bug-fix skill in the Bifrost skills repository, showing its SKILL.md body with When to use, Workflow and Safety sections](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/x4gq8350s5mk3wqle4iz.png) After creating the skill, we need to configure OpenCode as well, so that it can actually use the skills. For that we can paste the following code snippet in our OpenCode config: ```json { "permission": { "skill": { "*": "allow" } } } ``` The `*` there means every skill is allowed. You can be pickier if you want, since the same block takes `deny` and `ask` alongside `allow`, and the keys accept wildcards, so something like `internal-*` can be denied while everything else stays open. And you will be able to use it inside OpenCode anytime you want! Now, the next most useful feature that I found is setting up custom log headers. Let's talk about that then! ## Custom Log Headers So, while I was primarily using my key on OpenCode, I started to use it on jcode as well. But I was unable to identify from the logs which request was coming from which harness. Hence I decided to find a solution, and found that we can create custom log headers in Bifrost from the Log settings. It was pretty simple to set up. The idea is straightforward: you name a header, and from then on Bifrost copies that header off every incoming request and stores it in the log entry's metadata. I added the header name `X-Request-Source`. After that, I configured OpenCode to actually send that header, by pasting this in the config file: ```json { "provider": { "bifrost-local": { "npm": "@ai-sdk/openai-compatible", "name": "Bifrost Local", "options": { "baseURL": "http://localhost:8080/v1", "apiKey": "{file:./bifrost-virtual-key}", "headers": { "X-Request-Source": "opencode" } } } } } ``` And, that's it! ![The Bifrost logs table with an extra X-request-source column, showing opencode against every request](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/4loyitgxbfeatne9i0tw.png) As you can see in the screenshot above, there's now an extra column in the logs, and I can tell that these requests are coming from OpenCode. Repeat the same block in your other harness with a different value, and the whole picture separates out. There's also a shortcut I found afterwards, which I'd have used if I had known about it. Any header you send with an `x-bf-lh-` prefix gets captured into the log metadata automatically, without configuring anything on the Bifrost side at all. The prefix gets stripped and whatever is left becomes the key. So `x-bf-lh-source: opencode` would have got me the same result with one less step. It was as simple as that! ## So, What's Actually Worth Using? Honestly, more of it than I expected, and a bit annoyingly so. Every one of these had been sitting in that sidebar the whole time I was happily using Bifrost as a fallback router and nothing else. The MCP gateway is the one I'd tell you to try first. Moving every MCP out of individual harness configs and into one place, then handing each harness a key that decides what it can see, fixed a mess I had stopped noticing because I'd been living in it for so long. Virtual keys are the ones I'd keep the tightest grip on, now that I know the limit gets checked against what you have already spent and not against what you're about to spend. And custom log headers took about two minutes and solved something I'd been squinting past for weeks. The prompt and skills repositories I'm still making my mind up about. Testing a prompt properly before shipping it is genuinely useful, and the versioning is better than what I was doing before, which was nothing. But I've not used them long enough to tell you whether they replace the way you already keep your prompts, or just sit beside it. So if you're running Bifrost as a gateway and never got past routing, go open that sidebar. There's more in there than I expected, and all of it stays local. And if you want to get a taste of the enterprise version, then you can easily [book a demo](https://www.getmaxim.ai/bifrost/book-a-demo). ![Please like, share and follow](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/p08hea4cd4ubq3zystk1.png) And if you try any of these, or you've found something in Bifrost that I've still not touched, tell me about it, or come find me on [X](https://x.com/swapnoneel123). ## Trying Bifrost: An AI Gateway That Simplified My Setup URL: https://www.swapnoneel.site/blog/trying-bifrost-ai-gateway Date: 2026-07-30T00:00:00.000Z Summary: A hands-on test of Bifrost, Maxim AI's open-source gateway for 20+ AI providers, covering local OpenCode setup, automatic fallbacks, and complexity-based routing. [Bifrost](https://docs.getbifrost.ai/overview) is Maxim AI's high-performance, [open-source AI gateway](https://github.com/maximhq/bifrost) that unifies access to 20+ providers through a single OpenAI-compatible API. I tried it locally because I use several model providers and was tired of managing a separate API key and connection for each one. Bifrost is written in Go, and its gateway includes a built-in web UI for provider configuration and real-time monitoring. For this setup, the important part is that OpenCode can keep one base URL while Bifrost handles provider credentials, routing, [automatic failover](https://docs.getbifrost.ai/features/retries-and-fallbacks), and request logs behind it. If you've used LiteLLM before, it may sound familiar on paper, but the experience is pretty different in practice. LiteLLM is a Python library and proxy you configure and run yourself; Bifrost ships as a standalone Go binary with a full web dashboard baked in, so there's no separate observability stack to stand up just to see what's actually happening to your requests. That dashboard ended up being the thing I used the most, as you'll see below. I wired it into [OpenCode](https://opencode.ai), an open-source coding harness similar to Claude Code and Codex. I'm using an OpenCode Zen key and a Gemini key, and together, these give me access to multiple SOTA models for free, without touching a separate dashboard for each provider. ## Installation and Setup The local setup took two commands. First, I installed the Bifrost CLI using this command: ```bash npx -y @maximhq/bifrost ``` Note: This requires Node and NPM to be installed on your machine, otherwise it won't work. If you want to know how to install and manage node versions, [you can follow this blog that I've written earlier](https://www.swapnoneel.site/blog/nodejs-npm-nvm). I ran Bifrost with a project-local application directory so its configuration and logs stayed with this setup. The [Bifrost documentation](https://docs.getbifrost.ai/quickstart/gateway/setting-up) also covers system-level installation. ```bash npx -y @maximhq/bifrost -app-dir ./my-bifrost-data ``` I ran this command in my working directory, and this will create the configuration files and the logs db using SQLite. The same command starts the dashboard on port 8080. I used it to add the provider API keys that OpenCode would access through Bifrost. ## Connecting Your Providers Now it's time to grab your API keys, and connect it to Bifrost. As I've mentioned previously, I will be using Zen and Gemini. Both of these have generous free tiers, and getting the API keys doesn't require any credit card details. ![Adding an OpenCode Zen key in Bifrost, with allowed and blocked models configurable per key.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ifxbew6g3hran2gduwhf.png) As you can see from the above screenshot, adding your keys is pretty simple. You just need to select your provider, assign a name to your key and you are good to go! Additionally, you can also add allowed models to your specific key, because most of these keys come with a lot of available models, and if you don't want to use all of them, or limit your pool, you can do it from here as well. ## Integrating With OpenCode Now that you have connected your providers to Bifrost and it is already running, we can now integrate Bifrost and these models directly to OpenCode. Now, adding a connection like Bifrost means you have to manually edit the config file. Based on your operating system, the location of the config file might vary. More detailed info about that you can check on OpenCode's official documentation. But the file that we need to edit is `opencode.json`. In the provider block, we have to add something similar to this: ```json "bifrost-local": { "npm": "@ai-sdk/openai-compatible", "name": "Bifrost Local", "options": { "baseURL": "http://localhost:8080/v1" }, "models": { "opencode-zen/big-pickle": { "name": "Big Pickle via Bifrost" }, "gemini/gemini-2.5-flash": { "name": "Gemini 2.5 Flash via Bifrost" } } } ``` Note: I gave this its own `bifrost-local` provider block instead of just pointing OpenCode's built-in `openai` provider at Bifrost, the way Bifrost's own docs show it, because I wanted it visually obvious in the model selector which models are going through Bifrost versus hitting a provider directly, in case I ever wire up a real OpenAI key in the same config later. Either approach works functionally; this is just how I like to keep them apart. Now this will vary based on your selected model and provider. It is better if you can check it out from the Bifrost docs itself. Now that we are connected, we can launch OpenCode from our terminal (or app), and in the model selector we will see something like this: ![Both models now show up in OpenCode's model selector, routed through Bifrost Local.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/si2cvhaifiesppmkgg4m.png) Now we can select this model and use it to do our task on OpenCode. But wait, till now I just described how Bifrost sits between your model provider and harness. So, let's get to the crux and find out what more things we can do with Bifrost! ## Monitoring and LLM Logs Bifrost gives us a clear understanding of how our models are performing, the amount of tokens they are consuming, the latency and the cost as well. In the screenshot below, you will be able to see how Bifrost does that: ![Bifrost's live LLM logs: requests, success rate, latency, tokens, and cost, all in one dashboard.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/5gy16zixrg4t5slxic89.png) We can see and check the detailed logs as well. All of your data is stored locally and nothing gets sent to the cloud, other than the messages we're sending to the model providers, which is obvious. Everything is kept air-gapped. ## Model Routing using Automatic Fallback Now the most interesting part: how do we automatically route the models? During production, one model might fail to respond, and it's kinda common. So for those scenarios, we can set rules like: If model A is not available, then use model B. This is the simplest version though. Using CEL expressions, we can create custom routing rules for almost anything that we want. If models are available and we have a specific logic in mind, we can implement that in Bifrost easily. So, for now, let's create a simple rule, such that: if Gemini models aren't available, we will route the traffic through Zen models instead. ![The fallback rule: if Gemini's gemini-2.5-flash fails, route to OpenCode Zen's Big Pickle instead.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ug7deh69apmje8fb8h8m.png) As you can see in the screenshot above, I have created a global rule with maximum priority, such that if the provider is `gemini` and the model is `gemini-2.5-flash`, and if the user is using that specifically, we will fall back to `opencode-zen/big-pickle` instead. Now as the rule is set and applied, I will temporarily disable the gemini services to see what happens. Let's come to OpenCode, and type a "hi", and let's see what happens. ![Sent a quick "hi" from OpenCode with Gemini disabled, no visible interruption.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/y7rxucfli3ivtomvff6i.png) As you can see, the user didn't get any interruption at all. Let's check the logs to find out what actually happened here. ![The logs confirm it: Gemini errored out, and Bifrost silently fell back to big-pickle.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3njozpqr6g3ytrb177ar.png) As we can see, the gemini model gave an error, and it automatically fell back to big-pickle, and gave me the response. The fallback happened inside the gateway, so OpenCode received a response without needing to know that Gemini had failed. ## The Complexity Router Now that we have understood Model Routing, let's see how we can determine and set what kind of requests should go to which kind of models. For example, we can route trivial queries to cheap models, while preserving the expensive ones for difficult tasks. The idea is simple: divide the incoming requests into four tiers: simple, medium, complex and reasoning. So I've configured my complexity routing profile, and you can easily do it as well based on your own requirement. Here's my profile: ![My complexity routing profile: tier boundaries and keyword lists that decide simple, medium, complex, and reasoning requests.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/jn984noo2rlwqhqq6b3q.png) For my simple and medium queries, I want the gemini model to handle that, and for complex and reasoning-based queries, I want them to go to big-pickle. Now, we need to create custom routing rules for that. ![The two complexity routing rules: simple and medium traffic to Gemini, complex and reasoning traffic to Big Pickle.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/f0w7uf10mauqav9dopjv.png) Now that the rules have been created, let's test them, and check the LLM logs. I'll send two queries from OpenCode: - `Hello, briefly define REST API.` (expected to go to gemini) - `How to debug an async API authentication failure step by step, explain the root cause, and recommend an architecture fix.` (expected to go to big-pickle) ![The simple query went to Gemini, the complex one to big-pickle, exactly as configured.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/8axw0rg5qpcc7kx5dtwq.png) And we can see in the screenshot above that it worked exactly how we intended. ## Bifrost Can Do a Lot More Model routing and the complexity router are the two features that got me the most excited, but Bifrost isn't limited to just these two. It also ships guardrails, virtual keys, and cluster mode for scaling across machines, and that's still on my list to explore. Honestly, I loved this. Setting it up took maybe fifteen minutes end to end, and it quietly fixed a problem I'd been living with for a while: juggling separate API keys and dashboards for every provider I wanted to test. The fallback and complexity routing worked exactly the way the docs said they would, no surprises, and that alone made this whole exercise worth it. ![Please like, share and follow](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/s436tk6aszvy9g079vjt.png) If you're juggling more than one model provider and keep swapping keys by hand, give Bifrost a shot. And if you've already tried it, let me know what you built with it, and also if the enterprise version intrigues you, you can [book a demo as well](https://www.getmaxim.ai/bifrost/book-a-demo). ## Nobody's Testing AI Coding Agents Enough URL: https://www.swapnoneel.site/blog/testing-ai-coding-agents Date: 2026-07-24T00:00:00.000Z Summary: 40-62% of AI-generated code ships with flaws. Testing AI coding agents stopped being optional, here's what I learned being paid to do it. Code review used to be the part everyone complained about. Slow, nitpicky, the thing standing between you and shipping. And for a while, AI coding agents made it feel optional. The agent writes the code, the code compiles, the tests pass, ship it. The industry-wide numbers are worse than what most developers see in their own repositories. Somewhere between 40 to 62% of AI-generated code got shipped with security or design flaws by March 2026, and roughly one in five breaches this year traces back to AI-written code, [according to industry analysis on the verification gap](https://futurumgroup.com/insights/why-ai-coding-agents-need-an-independent-review-layer-trust-not-output-is-the-bottleneck/). Code generation got really fast. Verification did not pick up the same pace. Testing AI coding agents properly is where that gap lives, whether your team has staffed for it or not. ## What's actually breaking? In late June 2026, security researchers at Adversa AI disclosed something called GuardFall, a shell-interpretation bypass that worked against 10 of 11 popular open-source AI coding and computer-use agents, [including Aider, Cline, Goose, and OpenHands](https://securityaffairs.com/194546/ai/guardfall-flaw-hits-10-of-11-popular-open-source-ai-agents.html). The agents were checking the raw command text for danger before running it, but bash rewrites that text through quoting, substitution, and expansion before it actually executes. So a command that looks harmless to the safety check can still detonate once the shell gets its hands on it. And only one tool in the survey actually held up! ![The check reads the label. It never sees what the parcel turns into.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/4pd0cgpsjrqtwi6jdlro.png) And it's not just an edge case for people running agents locally. A scan of 5,600 vibe-coded apps already in production [found 2,000 highly critical vulnerabilities and 400 exposed secrets](https://digitalbiztalk.com/article/vibe-coding-is-killing-open-source-the-2026-developer-crisis), some of them exposing medical records and payment information. Georgia Tech's Vibe Security Radar tracked the trend line getting worse, and not better: 6 confirmed AI-generated vulnerabilities in January 2026, 15 in February, 35 in March. ![Even Kernel doesn't like where this evidence is pointing.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/zsaehsmgv4kvekhp4cov.png) ## Why is this happening if the models got so much better? Better models didn't remove the need for verification, they just moved the bottleneck. Generating a solution stopped being the hard part a while ago. Deciding whether you can actually trust that solution is the hard part now, and [55.4% of enterprise decision-makers already name agent reliability and hallucination management as their top production challenge](https://futurumgroup.com/insights/why-ai-coding-agents-need-an-independent-review-layer-trust-not-output-is-the-bottleneck/). The code compiles, the tests pass, and reviewers still have to reconstruct what the change was even trying to do before they can tell if it's safe. ## I've actually had this job, and it's not glamorous I recently worked with an early-stage startup testing a self-evolving agent. My job was to catch failures before its core users saw them. I also built an internal evaluation chain that pulled the agent's logs and checked whether its learning from feedback matched real scenarios, whether it hallucinated improvements, and whether it tried to work around its own guidelines. The same tool reported latency and likely slowdowns. "It seems to be working" was not enough; I needed numbers. ![Someone has to stand between the output and the door. That someone had a job title.](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/9hvhj0y9fidrjdbapalx.png) Before that, I spent time at Keploy building and improving sample apps specifically to demo API testing. So when I say testing agent output is a real, staffable job and not a checkbox, that's not a hot take pulled from a headline, it's what I got paid to do. And to be fair to the agents themselves: they are genuinely fast, and genuinely useful. I use Claude Code, Antigravity and Codex daily, and I'm not about to pretend otherwise. The problem was never that the code they write is bad on average. The problem is that "on average" is exactly the wrong bar for security and correctness, because the failures cluster in the 5-10% you didn't specifically check. (And yes, this post was drafted by an agent skill I built, and I'm going to go through and edit it before it goes anywhere near publish. That's not irony, that's the actual point: the draft can get the facts and the structure right, but deciding which of my receipts actually belong here, and how hard to steelman the agents, is still a job for a human. Mine, in this case.) ## What should you actually do differently? Stop reviewing agent output the way you review your own code, and start reviewing it the way you'd review a fast junior developer's very first PR: assume competence, verify everything, especially the parts that touch execution. Concretely: never let an agent pipe raw, unreviewed strings into a shell without a real evaluator in between, GuardFall exists because teams assumed string-matching was enough. Budget actual human review time as a fixed cost of using these tools, not a nice-to-have. And track your own vulnerability trend line the way Georgia Tech tracked the industry's, because "it hasn't broken yet" is not the same thing as "it's fine." And testing an agent isn't only about catching bugs before they ship, it's also about watching what the agent quietly costs you over time. That's exactly why I built that internal latency-tracking tool in the first place, numbers on slowdowns catch problems long before a user ever complains. [Bifrost](https://docs.getbifrost.ai/overview) is Maxim AI's high-performance, [open-source AI gateway](https://github.com/maximhq/bifrost) that unifies access to 20+ providers through a single OpenAI-compatible API. For agent testing, that gateway position creates one record of requests and responses across models and providers. Its [built-in observability](https://docs.getbifrost.ai/features/observability/default) records inputs, outputs, provider, model, tokens, cost, latency, errors, and status for each call, so teams can compare agent behavior and operational cost without building a different logging path for every provider integration. I use these agents in my own workflow, but testing them is part of the work. Teams need to staff and plan for it instead of treating it as a final checkbox. If you're building your own testing layer for an AI tool, or you've been burned by one that didn't have one, I'd genuinely like to hear about it, drop it in the comments. I write more about agent tooling and building with AI at [swapnoneel.site](https://www.swapnoneel.site), including [how I built a self-improving writing agent](https://www.swapnoneel.site/blog/make-ai-write-in-your-voice), and you can find me on [X (swapnoneel123)](https://x.com/swapnoneel123). ![Your turn. What did your testing layer catch?](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/vr4zw9hox2yybrgbhzqs.png) ## Kimi K3 and the Rise of Open Weight Frontier Models URL: https://www.swapnoneel.site/blog/rise-of-open-weight-frontier-models-kimi-k3 Date: 2026-07-23T00:00:00.000Z Summary: Kimi K3 just landed a 2.8T open weight model a hair behind GPT-5.6 Sol and Fable 5. Here's why open weight frontier models finally matter. Apparently everyone is talking about the launch of Kimi K3 right now. So I wanted to share my two cents on this, especially for those who consider this to be just a "cheap chinese model". Because honestly, that joke doesn't land well anymore. Here is the actual reason why I'm saying so. Moonshot AI released Kimi K3 on July 16, 2026, a 2.8 trillion parameter open weight model, and it landed at #4 on the Artificial Analysis Intelligence Index, just behind Claude Fable 5 and GPT-5.6 Sol, and ahead of Claude Opus 4.8. Full weights are dropping on July 27. Pricing is 3USD and 15USD per million input and output tokens respectively, a fraction of what the closed labs charge. That's why open weight frontier models are suddenly a real conversation and not just a budget footnote. ## The old belief was that closed labs own the frontier For most of the last two years, the assumption was simple: if you want the smartest model, you pay OpenAI or Anthropic, period. Open weight models were the budget option, good enough for chatbots and side projects, but never good enough for the actual frontier. But those walls started shaking when we got GLM 5.2 from Z.ai, just a few weeks ago. And then came Kimi K3, which is the moment that assumption stopped being obviously true. Not because it beats GPT-5.6 Sol and Fable 5 outright, it doesn't, but because the gap has gotten small enough that "just use the closed model" is no longer an automatic decision. According to Nathan Lambert's analysis on Interconnects, the gap between open and closed, and between US and Chinese labs, has shrunk from a debated 6 to 9 months down to something closer to 3 to 5 months ([interconnects.ai](https://www.interconnects.ai/p/kimi-k3-the-open-weights-escalation)). ![Proprietary AI wall vs open-weight model accessibility](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/n2oyhuh4p1el7u73bhrn.png) ## What's actually inside Kimi K3 The specs are genuinely wild. It's a mixture-of-experts model with 896 experts, and it only activates 16 of them per token, so despite being 2.8 trillion parameters total, the compute cost per token stays manageable ([kimi.com](https://www.kimi.com/blog/kimi-k3)). It ships with a 1 million token context window, native vision, and a new attention mechanism called Kimi Delta Attention. On raw benchmarks, K3 takes first place on Program Bench, SWE Marathon, BrowseComp, and Frontend Code Arena. Program Bench specifically jumped from 53.6 to 77.8 over its predecessor, a 45% jump ([wan27.org](https://wan27.org/blog/kimi-k3-benchmarks)). In blind developer testing on Arena, people preferred Kimi K3 over both Fable 5 and GPT-5.6 Sol for front-end coding specifically ([codersera.com](https://codersera.com/blog/kimi-k3-benchmarks-comparison-2026/)). ![Kimi K3 MoE architecture with active expert routing](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/8e30q00qh3ztb3x6cfzi.png) ## Does that mean the closed labs are done? Well, not entirely. GPT-5.6 Sol and Fable 5 still sit ahead on the general Intelligence Index, around 59 and 60 versus K3's 57. And Moonshot's own success became a problem within days. Demand strained their compute capacity hard enough that they had to pause new subscriptions. That's not a small footnote. Running a 2.8T model at scale is expensive even when you're the one giving the weights away for free, and it shows that "open" doesn't automatically mean "infinitely available." And here's the honest catch on price too. K3 spends way more tokens in reasoning. So, Sol and Fable both tend to get to an answer in noticeably fewer tokens than K3 needs for the same task, so once you look at cost per task instead of cost per million tokens, the gap almost closes. The sticker price makes K3 look like a steal, the actual bill at the end of the month is a lot closer. ![Cost per task comparison between proprietary and open-weight models](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/uinoqqnkifadoeg9a002.png) ## Why open weight frontier models actually matter to you I ship AI products for a living, and most of my projects live and die by which model I pick underneath them, and cost per task isn't some abstract line item for me, it directly decides whether a feature is worth shipping. When a model that's a few points behind on intelligence lands close on actual cost and you can self-host it once the weights are out, that's not a footnote, that's a real decision every team building on LLMs now has to make. I ran into a version of this same tradeoff when I wrote about [testing AI coding agents](https://www.swapnoneel.site/blog/testing-ai-coding-agents), model choice was never just about the leaderboard, it was about what actually held up under my own usage. And there's a bigger reason than cost. A model whose weights you hold cannot be shut off by someone else's pricing decision, rate limit, or policy change. That's the sovereignty argument people keep making about open weight models, and it stops being theoretical the moment your product depends on an API you don't control. ![API key dependence vs open-weight model ownership](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ttfanykfdtcz6ice3x64.png) ## The verdict Open weight models are no longer the consolation prize. Kimi K3 is proof that you can be a handful of benchmark points behind the absolute frontier and still be the more rational choice for a huge chunk of real work, especially coding. GPT-5.6 Sol and Fable 5 are still the smartest models on the planet right now, and if you need every last point of reasoning, use them. But if you want to not depend on someone else's uptime, and you're fine with the actual bill landing close either way, going and actually trying K3 instead of assuming the closed model wins by default is worth your afternoon. That's just me though, and your workflow might be different depending on what you're actually building. ![Thank you graphic for open-weight AI blog](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/az7um68ztq915wabjbf0.png) If you're experimenting with model choice for your own AI products, drop a comment with which model you've moved to since K3 landed. ## Why Vercel is still my default for shipping frontend projects URL: https://www.swapnoneel.site/blog/vercel-frontend-deployment-default Date: 2026-07-13T00:00:00.000Z Summary: Why I keep reaching for Vercel for frontend projects — and where Cloudflare, Netlify, and Railway are the better choice. Last week, I was working on a client project with a fast approaching deadline. The work had already piled up, so I had to move really fast; I was constantly making changes, pushing them straight to GitHub, checking them through the preview link of the deployment, and going straight to the next task. And while doing so, I barely stopped and worried about hosting, because Vercel was already connected. And after successfully delivering the project within the stipulated time, it hit me that I probably could not have moved that quickly if the deployment itself had been another thing to manage. That made me realise: Vercel has been my default choice for a long time, and it is not because I am completely locked into the platform. From time to time, I still reach for other services like Cloudflare, Netlify, and Railway as well, but for my personal projects and fast development cycles, I somehow always end up coming back to Vercel. ![Vercel automatic frontend deployment workflow](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/gcum98tsutf93kuwjt2v.png) I mostly use Next.js, so I know the tech nerds out there will assume that, it is the entire reason why I choose Vercel, and that's a fair assumption to make, because it's partly true. Vercel develops and maintains Next.js, so of course it provides the best hosting for Next.js, but that's just one side of the coin. ## How I use Vercel in my projects If you check the projects section on [my portfolio](https://www.swapnoneel.site/work), you will find that most of the web projects I currently have are deployed through Vercel. And not all of them are Next.js applications; you will find projects with React, TanStack tooling, Node.js, and Bun as well. These are not just weekend experiments or hobby projects, either. Some of them have real users as well! Let me give you [Scholarian](https://scholarian.vercel.app) as an example. It is a research platform built on Next.js, and according to my latest project analytics, it currently has more than 75 active users and over 700 chat sessions. The funny thing is that I did not think twice about deploying most of these projects. I connected the repository, gave Vercel the required environment variables, and pushed the code. That absence of thought is the whole point. But then again, I also use Cloudflare Pages and Railway for actual work, so this is not a “Vercel is perfect and everything else is bad” argument. I have reasons for coming back, but I also know where the platform starts becoming the wrong tool. So, let's discuss! ## Why do I keep coming back to Vercel? **First of all, Vercel's preview deployment workflow!** It makes my development cycle much smoother. By default, every non-production branch can receive its own preview URL, and I can share that URL before merging the branch. That's extremely useful for catching visual problems before they reach production. A pull request may look completely fine during code review, but you can never know when the actual interface breaks at a particular viewport width. This has happened to me a lot. Just a few days ago, I shipped the near-final version of a project to one of my clients without noticing that, in the mobile version, a heading was overlapping one of the image assets. Preview deployments let people test the thing instead of trying to imagine it from a diff. ![Vercel preview deployment card sharing and visual QA](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/53o4fp681781ercsv3cd.png) For a solo developer and freelancer like me, this saves a lot of time because, as you can see in the below screenshot, Vercel adds a toolbar to preview deployments where collaborators can leave comments directly on the page. This was especially useful during hackathons, when we were short on time. And our team always communicated in that way, and my teammates would drop in and leave comments like "the link to this button is redirecting to the pricing page instead of the features page" or "the color is way too contrasty." The small catch is that they need a Vercel account to comment, and external collaboration has some plan-specific limits, so it is not entirely frictionless, but still, it is much easier than sending Loom videos, annotated screenshots, or five messages explaining which button or font your client or peers want. And they have an optional third-party integration as well that can convert a preview comment into a GitHub issue. This makes conversations with my clients and non-technical collaborators much easier! ![Vercel preview toolbar with comments and collaboration controls](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/8skbo3bygfl4pfwmfo0o.png) **The Next.js experience is the other reason I keep using it.** Vercel develops the framework, so features such as Incremental Static Regeneration, Server Actions, React Server Components, route handlers, and streaming work with very little platform-specific configuration, and I don't have to spend an afternoon figuring out how a new Next.js feature maps onto the hosting environment. Vercel covers that part for me by default. Now, to be fair, other platforms have improved a lot, and Netlify currently supports the major Next.js features through its OpenNext adapter, including Server Components, Server Actions, streaming, ISR, and Partial Prerendering. Cloudflare can also run Next.js using its own OpenNext-based adapter. So the difference is no longer that Next.js features simply do not work elsewhere, because that would be an outdated argument. The difference is that Vercel remains the first-party deployment target, and that means there is one less compatibility layer to worry about. And this removes a pain point for me, especially when I am using a newer framework feature. And that's the edge I'm actually talking about. For a normal static React or Vite application, this advantage matters much less, but for a serious Next.js project, it becomes my go-to option. ![Vercel Analytics dashboard showing visitor traffic and bounce rates](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/v8qp2p0klx6l09y43ly7.png) And then there's the DX at the dashboard level. These are minute things, but together, they make a big difference for me. For example, the environment variables are scoped per environment (local, preview, and production; all of them are isolated). Rolling back to any previous deployment takes two clicks. And the deployment logs actually tell you what failed, not just that it did, and because of that, they become much easier to fix if you are taking the “pasting it into Claude Code” route. ## What are the alternatives to Vercel? Well, when we are talking about the alternatives, **Cloudflare Pages** is the one that comes the closest. And we know how much tech Twitter is divided on this one, and how frequently we see their representatives fight each other on open threads regarding this (I enjoy watching those heated arguments, lol). And yeah, Cloudflare is genuinely fast, and [their edge network spans 300+ locations](https://www.cloudflare.com/network/), and for static content, the performance gap over Vercel is actually quite measurable. And what I appreciate most is that the pricing is much more predictable; because, first of all, there are no egress fees, and they also provide unlimited bandwidth on the free tier. And as a bonus, I have also seen them [helping start-ups from time to time as well](https://x.com/IanLandsman/status/2059289714264273337), which is a great initiative, in my opinion. ![Infrastructure control vs fast shipping speed comparison](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/j0aqe0r43weglq9cpqb3.png) I respect all of this, but the problem is that Cloudflare's Workers environment runs on V8 isolates, which is different from a standard Node.js runtime. And this is where I face the most problems. For purely static sites or projects that have lightweight edge functions, it's totally fine, but sometimes, with specific packages that exclusively require a Node.js runtime, you start to face error messages. And although `nodejs_compat` mode now supports a substantial portion of the Node API, the compatibility is still not perfect. There is also a trade-off in how the two platforms approach infrastructure. If you want databases, KV stores, or smart routing in your project, you must understand Cloudflare's broader ecosystem, like D1, KV, and routing rules, which is great when you want that level of control. But Vercel abstracts all of that by default. It is basically a trade-off of infrastructure control for speed, and I prefer Vercel's simpler deployment workflow in this regard. **Netlify** was my original platform before I switched. I have nothing against it, honestly. It is very similar to Vercel in a lot of ways. But Vercel's integration with Next.js, which I just discussed in detail in the previous section, makes Netlify feel like it's one step behind. Features like Server Actions and React Server Components work natively on Vercel, while on Netlify, they have to go through adapters that often lag behind new framework releases, which is a big compromise. And another thing: Netlify's core CDN infrastructure also has fewer edge locations than Vercel's 100+ node network, and that's visible in the global TTFB numbers as well. I'd still use Netlify for a simple static site with a form or two because their built-in form handling is actually clever. But for a Next.js project, Vercel is my primary choice. Now, for **Railway**, it's a bit different, and I use it when I need a persistent backend, like maybe a WebSocket server, a background job, or something that can't be serverless. In Scholarian, I have a long-running task where the background worker has to produce a long report using Gemini, and that process generally takes three to four minutes, so I switched the backend of my app to Railway. For Vercel, that's where it genuinely breaks down. If you need a long-running process, you're either doing something hacky with edge functions or you're reaching for a different platform. And there are a couple of good options besides Railway, and for that, [Encore](https://encore.dev) would be my personal recommendation. ## Where Vercel actually falls short **The pricing model!** And that's the part I like the least. The free Hobby plan is capped, so it cannot generate an on-demand surprise bill, but if a hobby project exceeds its allowances, it may be paused or restricted instead. That's why I keep an eye out for my portfolio site, because that's the one that gets the most traffic. The bigger billing concern begins as soon as you switch to the Pro plan, where usage beyond the included credit can be charged across multiple resources. Being mindful enough is particularly important here, because I have seen a lot of posts on Reddit and X where developers have complained about the same issue. Vercel Pro currently has a 20 USD monthly platform fee, which includes one deploying seat and 20 USD of usage credit. And for additional developer seats that can deploy or configure the projects, they will cost you another 20 USD per month, but the read-only viewer seats are free. And that combination can become difficult to predict when a project grows. Vercel provides spending alerts and lets paid teams configure actions such as pausing projects after reaching a limit. Hence, it's better to enable those controls instead of assuming that traffic will always stay predictable. Sudden bot traffic, a poorly optimized function, image transformations, or a sudden spike in legitimate users can all consume usage faster than expected. So, it's always better to keep those factors in mind so that you don't get overcharged accidentally. **The other limitation is compute.** Vercel Functions can handle APIs, server-rendered routes, streaming, and other request-driven tasks, and the current function limits are far more generous. But if your application requires a continuously running background process or custom Docker containers, Vercel isn't the right fit. There are platforms like [Render](https://render.com) or [Northflank](https://northflank.com) that are built for that kind of workload. Vercel is a frontend cloud, so the moment you need full-stack infrastructure, you're pairing Vercel with something else anyway. Hence, the title of my blog says why I prefer it for frontend projects, and not full-stack projects! And then there is also vendor lock-in, although I do not think it is as simple as people make it sound. And it's not limited to Vercel either; almost every service provider has its own kind of vendor lock-in. A static React or Vite project is easy to move, but a Next.js application that depends heavily on Vercel’s caching behavior, image optimization, routing, integrations, and deployment settings will take more effort to migrate. The more platform-specific behavior you adopt, the less portable your application becomes, and that's true for Vercel, Cloudflare, AWS, and almost every other cloud platform. I have not experienced the issue myself, but I have seen people complaining about it, so I included it in the blog. ## Is the Vercel free tier actually good enough? For most side projects, yes! And their [Hobby plan](https://vercel.com/pricing) is free for personal use and is pretty generous. It comes with unlimited projects, automatic HTTPS, custom domains, preview deployments, and 100 GB of bandwidth per month, and that's mostly enough for personal use. And I never felt the need to purchase their paid Pro plans. But the moment you add a team or need more bandwidth or function execution, you have to go to the Pro plan that starts at $20/month for each member. ## What is the verdict? Vercel is still my default for frontend deployment and a part of my development cycle. The preview URL workflow alone has saved me more debugging cycles than I can count. I've tried the alternatives in actual projects, and none of them gave me back the time I was spending on deployment issues. That said, if you're cost-conscious and don't mind the learning curve, then Cloudflare is still a great choice. And if you need a backend, pick Railway and point your Vercel frontend at it. And if you've moved away from Vercel for something specific, or if you have a setup that works better for you, I'd genuinely love to hear about it in the comments! ![Thank you graphic for Vercel deployment blog](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/tqs4v53py7hbpl3fdtgz.png) And if you want to see the kinds of projects I've actually shipped on Vercel, check them out at [my site](https://www.swapnoneel.site). Also, you can find me on [X](https://x.com/swapnoneel123) or [GitHub](https://github.com/Swpn0neel) if you want to talk or connect. ## GEO for Developers: Get Cited by ChatGPT and Perplexity URL: https://www.swapnoneel.site/blog/geo-for-developers Date: 2026-07-08T00:00:00.000Z Summary: GEO for developers, minus the agency fluff. Here's what actually matters if you want ChatGPT and Perplexity to cite your blog posts in 2026. Have you ever asked ChatGPT or Perplexity a coding question and get a suspiciously specific, correct answer, with zero link back to whoever actually wrote it? Yeah, that's been happening to my blog too, and I finally sat down to fix it. So here's the real answer: GEO for developers doesn't need an agency or a 40-page audit. It just needs three simple things: a properly structured content so a model can lift one paragraph and have it make sense on its own, real evidence instead of vague claims, and let the right bots get into your `robots.txt`. That's genuinely most of it, and in this blog I'll walk you through exactly how! ![AI search engines extracting content without source attribution](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/a877ztvlh1in45i8vcxu.png) ## What is GEO, actually? GEO stands for Generative Engine Optimization, and it's the practice of writing content so AI answer engines (ChatGPT, Perplexity, Google's AI Overviews) quote it directly instead of just ranking it in ten blue links. It's pretty self-explanatory, and it sits right next to SEO (Search Engine Optimization). We used to optimize purely for search engines like Google, but the times have changed, so now you're writing for generative engines too, Perplexity, Gemini, ChatGPT, all of them. The term comes from an actual peer-reviewed study, presented at KDD 2024 by researchers from Princeton, Georgia Tech, and IIT Delhi, and it's become its own line item in 2026 marketing budgets. [The paper's numbers are wild](https://www.omnibound.ai/blog/generative-engine-optimization-statistics): adding statistics to a page boosted its visibility in AI answers by 41%, and content optimized for generative engines improved visibility by up to 40% overall. ## Why doesn't most GEO advice fit a dev blogger? Here's the thing though, and I say this as someone who's done SEO freelancing and used Semrush since my first year of college: almost every GEO guide I found while researching this is written for a marketing team running brand-mention trackers across a hundred pages. That's not you if you're publishing one post a week on your own domain, or worse, on Hashnode. You don't need consensus-signal dashboards. You need to know which five things to do to your next post, and that's what I'm giving you. ## What actually works in GEO? **Answer the question in your first 40-60 words, standalone.** [Roughly 44% of everything AI engines quote comes from the first third of a page](https://www.omnibound.ai/blog/generative-engine-optimization-statistics), so don't bury your point under three paragraphs of throat-clearing. Say the thing, then explain it. If you notice carefully, I have already mentioned the three GEO optimisation steps in the introduction itself. **Phrase your headings as questions.** Not "Benefits of X", but "Why does X matter?" or "What is X?". This is also just a more natural way to write, so it's a rare case where the AI-friendly move and the human-friendly move are the same move. Check how I have framed the headings of each section =] **Every section has to make sense if someone rips it out of the page.** AI engines lift paragraphs, not entire posts. If your section starts with "This also means...", restate what "this" is. Small habit, big difference. **Put real numbers in, with sources.** Not "many developers prefer X", but "X handles 50,000 requests per second, per their own benchmark, published in June 2026". Vague claims don't get quoted. Specific, sourced ones do. You will find multiple such statements in this blog itself, and also with linked citations. **Fix your `robots.txt`.** This one's just a config file, and most bloggers never touch it (yes, that's a real file sitting on your domain right now, doing nothing). [The bots you want to allow for citations are different from the bots that scrape for training data](https://www.mersel.ai/blog/how-to-block-or-allow-ai-bots-on-your-website): `OAI-SearchBot` and `PerplexityBot` are the ones fetching pages to answer live questions, while `GPTBot` and `ClaudeBot` are the training crawlers. You can allow the first pair and still block the second, if that's the line you want to draw. ## Should you bother with llms.txt? Honestly? Probably not yet, and I want to be straight with you about this because most GEO posts won't be. The idea is simple: drop a markdown file at `/llms.txt` summarizing your site so a model doesn't have to parse your HTML. [Over 844,000 sites have added one already](https://www.mintlify.com/blog/what-is-llms-txt), including Anthropic's own docs. But no major AI company has confirmed they actually read it, and Google's John Mueller called it a "temporary crutch" that isn't done for search at all. So add one if you want, it costs you ten minutes and can't hurt. Just don't mistake it for the thing that's going to get you cited. That's the structure and the sourcing, not the file. ![Comparing lightweight llms.txt against structured site evidence](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/gvsazrctmc8kc6t6lrub.png) ## What if you don't even own your blog? If you cross-post to Hashnode or Dev.to like I do, you don't control the `robots.txt` on that domain, and you can't add schema markup either. That's the platform's call, not yours. What you can still control everywhere: the content structure itself, and your canonical URL. Always point the canonical tag back to your personal site, always write the answer-first paragraphs regardless of platform, and let the schema/`robots.txt` tactics apply fully only where you actually own the domain. ![Structured data distribution across owned web properties](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/xobsofohyefwrq57pkq2.png) ## Is GEO worth your time? Yes, but not the version most people are selling you. I recently rebuilt my own blog-writing process around exactly this, direct-answer blocks first, real stats with sources, extractable sections, and it's honestly made the drafts read better for humans too, not just for whichever bot happens to crawl them. I wrote up [the whole build here](https://www.swapnoneel.site/blog/make-ai-write-in-your-voice), if you want the longer version. ## FAQ **Do I need to block GPTBot to protect my writing?** That's a separate decision from GEO. Blocking `GPTBot` stops your content from training future models, but blocking `OAI-SearchBot` too would also stop you from showing up in ChatGPT's live search results. Decide which trade-off you actually want. **Will GEO replace SEO for blogs?** No, they overlap more than they compete. Structuring for extraction and citing real sources helps you rank in Google too. Think of GEO as SEO with an extra, stricter bar for evidence and standalone clarity. **How long before I see actual citations?** I don't have a clean number for this yet, since I only rebuilt my own process around it in mid-2026. Perplexity re-crawls constantly, so that's the faster feedback loop; ChatGPT search is slower and more selective about which pages it trusts. **Do I need schema markup if I only publish on Hashnode or Dev.to?** Not directly, since the platform controls that layer. Focus your energy on content structure and canonical URLs instead, those travel with you no matter where you publish. ![Thank you graphic for GEO for Developers guide](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/u9xplxu74ikwfrltmxfi.png) If you're building or rebuilding your own writing process around this, I'd genuinely love to hear what worked for you, drop it in the comments. You can also find me on [X (swapnoneel123)](https://x.com/swapnoneel123) or check out more of my work at [swapnoneel.site](https://www.swapnoneel.site). ## How I made an AI Agent write in my voice URL: https://www.swapnoneel.site/blog/make-ai-write-in-your-voice Date: 2026-07-06T00:00:00.000Z Summary: You can make AI write in your voice, but a prompt won't get you there... Let's be honest, AI-written blogs have a certain... vibe. You know it, I know it, and your readers can smell it from the first paragraph. But here's my take: you can make AI write in your voice, just not with a "generic" prompt. What actually worked for me is an agent skill with three parts: a voice profile built from seven of my real writing samples, a kill list of AI phrases, and a feedback loop that turns my edits into permanent rules. And here comes the twist, the blog you are reading right now is the very first output of that system! ![Generic AI prompt output vs personalized AI voice system](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/28o1zintr6j86wowkvn0.png) So, let me walk you through exactly how I built it, and you can judge for yourself whether it sounds like a human or not. ## Why does AI writing sound so... AI? Before fixing the problem, let's understand it from the ground up. An LLM is trained on billions of documents, so by default, it writes like the average of all of them. That's where phrases like "in today's fast-paced world"s come from, and those perfectly balanced conclusions that never pick a side. It's not that the model is dumb. It's that the average of a million voices is no voice at all. And your voice is the exact opposite of average. It's the specific way you break grammar rules, and the things you're willing to admit that others won't. I've written multiple technical blogs for different startups including Keploy, Devbytes and many more, and have been blogging on Hashnode since 2023. So when I asked AI to draft posts "in my style" with a simple prompt, the result was always the same: grammatically perfect, structurally neat, and absolutely not me. ## So, can you actually make AI write in your voice? Well, yes. But you have to show it, not describe it. ![Extracting writing mechanics from real blog samples](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/3s98sgz7952omd3ajmg3.png) "Write in a friendly, conversational tone" gives everyone on the internet the same friendly, conversational tone. What you need instead is a system that extracts the mechanics of your writing from real samples, and then enforces them like rules. Mine has three parts. ### Part 1: The voice profile I gave the agent seven samples of my writing: two journey blogs, one tutorial, one opinion piece, one comparison, three cold intros, and a small questionnaire about my tastes. And these are not just "any" samples, three of them are my past works that was cherry-picked by the system. And the other four were literally the topics given to me by Fable 5, so that it can understand my writing style better. But here's the important part, the profile it built isn't a list of adjectives. It's mechanics: - My sentences constantly open with And, But, So, and Now (this exact paragraph included). - My posts move forward by asking the reader's next question, and then answering it. - Every big claim needs a personal receipt with a number, not a vague "many developers say". - At most two "!!" per post. Yes, it literally counts them. And one more thing: newer samples always outrank older ones. My 2023 writing had habits I've dropped since, and the system knows my current voice wins every conflict. ### Part 2: The kill list The second file is a banned-patterns list. Every AI-ism I hate goes there: "delve", "seamless", "game-changer", rule-of-three sentences, em-dash chains, hedged conclusions that refuse to pick a winner, and emojis (all of them, I don't use emojis in my blogs, period). The rule is zero tolerance. If a banned pattern shows up in a draft, the agent doesn't just delete it, it rewrites the sentence the way I would say it. ### Part 3: The feedback loop (this is the part that actually matters) Now, the first two parts get you maybe 80% of the way. The remaining 20% is where every "write like me" tool I've seen gives up. Here's my loop: the agent writes a draft, I edit it like I normally would, and then a second skill diffs my final version against the draft. Every meaningful change gets generalized into a rule. If I cut a long intro once, that's a hypothesis. If I do it twice, it gets promoted to a confirmed rule that every future draft must follow. ![Human feedback loop creating reusable writing rules](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/yv9s4xjb0kxh7d4tjhz7.png) And there's a hard cap of 30 active rules. Why? Because this whole system runs on a smaller, cheaper model, and a smaller model follows 30 rules well and drowns in 80. The intelligence lives in the files, not the model. ## But does it learn from every single edit? Well, no. And this was a deliberate design decision. A one-off change (fixing a fact, rephrasing something topic-specific) teaches nothing about my voice, so it gets logged and forgotten. Only patterns become rules. Otherwise the agent would overfit to whatever mood I was in during one editing session. There's also one rule I consider non-negotiable: the agent can never invent a story about me. All personal facts live in a single profile file, and if a post needs an anecdote that isn't in there, the agent has to stop and ask me. An AI confidently fabricating a personal memory in your published blog is so much worse than a boring paragraph. ## Does it actually work? Honest answer: I don't fully know yet, and I won't pretend otherwise. This post is literally draft number one. The feedback loop has learned exactly zero rules from my edits so far, because there were no edits before this. You are looking at the "before" photo. If you can tell which sentences I touched after the agent wrote them, tell me in the comments, seriously! And another honest admission: setting this up took me more effort than just writing 2-3 posts by hand (I literally wrote four new blogs as an assignment lol, so that the LLM can infer my writing style better). The payoff only makes sense because it compounds, every post I edit makes the next draft closer to me. ![Fact verification guardrail blocking hallucinated AI claims](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/pivin0jtpppbxla1ajho.png) But the direction feels right, and I'm clearly not alone in thinking this way. The dev community has moved past one-shot prompting: Peter Steinberger's viral post ("you shouldn't be prompting coding agents anymore, you should be designing loops that prompt your agents") pulled 6.5 million views in June 2026 and [set the timeline on fire for a week](https://explainx.ai/blog/loop-engineering-coding-agents-claude-code-guide-2026). And the [Hacker News discourse in 2026](https://www.developersdigest.tech/blog/what-hacker-news-gets-right-about-ai-coding-agents-2026) has shifted from shiny demos to making agents repeatable and trustworthy. A writing agent with a feedback loop is just that same idea, pointed at a blog. ## What are agent skills, anyway? If the term is new to you, let's zoom out for a second. An agent skill is basically an onboarding document for an AI. It's a markdown file (usually called SKILL.md) with step-by-step instructions, plus supporting files it should read, that a coding agent like Claude Code loads before doing a task. Think of it like the difference between telling a new intern "write a blog" and handing them your company's full writing playbook. And the beautiful part is that skills are portable and dumb-model-friendly. I have orchestrated multiple agentic workflows, and the lesson from there was the same: agents don't fail because the model is weak, they fail because the instructions are vague. ## FAQ **How many writing samples do you need to clone your voice?** Seven worked for me, but coverage beats volume. One sample per content type (tutorial, opinion, comparison, narrative) teaches far more than ten samples of the same type, because your voice changes with the mode. **Can this work with a cheaper model?** That's the whole point. The voice profile, kill list, and rules carry the intelligence, so a smaller model just has to follow instructions. Save the expensive model for building the system, not running it. **How do you stop the AI from making up facts about you?** One canonical profile file, and a hard rule: if the fact isn't in the file, ask the human. Never generate a personal claim from thin air. Accepting that AI can't do the entire job for you, and you have to keep yourself in the loop, creates the difference. **Does this replace writing?** No, and I don't want it to. It replaces the first draft and the SEO chores. The opinions and the final edit are still mine, and honestly, that's the part I enjoy anyway. Even the current sentence that you are reading right now, was actually inserted by me during the edit. ## So, should you build one? If you publish regularly, yes. Build the voice profile. Really do. But don't skip the feedback loop, because without it you've just built a fancy prompt that will drift back into AI-slop within three posts. And start smaller than I did: pick your five most representative pieces, extract the mechanics (not adjectives!), list ten phrases you'd never say, and make reviewing the diffs a habit. If you want a more detailed analysis about the system, just comment down below and I would be happy to help you all! I'll be sharing more about this system as the feedback loop matures, including the numbers on how many edits it actually takes before drafts start needing none. If you want to follow that experiment, you can find me on [X (swapnoneel123)](https://x.com/swapnoneel123) or check out my other works at [swapnoneel.site](https://www.swapnoneel.site). ![Thank you graphic for AI writing voice blog](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/mnf69thhecu599edac1q.png) And that's a wrap! Have you tried making AI write like you? What worked, and what came out sounding like a LinkedIn bot? I would love to hear your experience. Thank you for reading, and have a nice day ahead!! ## Chaos Testing Explained: A Comprehensive Guide URL: https://www.swapnoneel.site/blog/chaos-testing-explained-a-comprehensive-guide Date: 2025-01-09T01:26:15.000Z Summary: Table of Contents Chaos testing, also known as chaos engineering, is one of the most-used methodology to test the resilience and reliability of systems, and is a key part of modern resilience testing practices. Originating from Netflix’s famous Chaos Monkey tool, chaos testing has become a key practice in building robust distributed systems. In this [...] Chaos testing, also known as chaos engineering, is one of the most-used methodology to test the resilience and reliability of systems, and is a key part of modern [resilience testing](https://keploy.io/blog/community/why-apps-crash-and-how-resilience-testing-can-help "resilience testing") practices. Originating from Netflix’s famous Chaos Monkey tool, chaos testing has become a key practice in building robust distributed systems. In this blog we’ll be diving into the realm of Chaos Testing and understand it in detail. So, let’s begin! ## **Why Chaos Testing is Crucial?** Nowadays, modern systems are increasingly distributed, running on cloud infrastructure and microservices architectures. While these designs offer scalability and flexibility, they also introduce complexities and potential failure points. This is where it ensure that systems can withstand real-world disruptions like server crashes, network partitions, or latency spikes, ultimately enhancing reliability and user trust. ## **Fundamentals of Chaos Testing** ### **What are the Core Principles?** 1. **Embrace Failure:** Assume that failures are inevitable and plan for them. 2. **Test in Production (With Safeguards):** Simulate real-world conditions to get accurate insights. 3. **Minimize Blast Radius:** Limit the scope of experiments to avoid widespread disruptions. ![Core principles of chaos engineering diagram](https://wp.keploy.io/wp-content/uploads/2025/01/What-are-the-Core-Principles.webp) ### **How it differs from other types of testing?** Unlike [load or functional testing](https://keploy.io/blog/community/all-about-load-testing-a-detailed-guide), chaos testing focuses on the system’s behavior under unexpected conditions rather than verifying its performance under normal operations. It’s less about finding bugs and more about uncovering systemic weaknesses. ### **What are the Key Goals of Chaos Testing?** - **Identify Weaknesses and** expose hidden vulnerabilities in the system. - **Validate Redundancy Mechanisms to e**nsure failover and backup systems function as expected. - **Improve MTTR and e**nhance the mean time to recovery after an incident. ## **The Chaos Testing Process** ![Step-by-step chaos testing process diagram](https://wp.keploy.io/wp-content/uploads/2025/01/The-Chaos-Testing-Process.webp) ### Step-by-Step Guide 1. **Define Steady-State Behavior:** Identify and quantify what a healthy system looks like. For example, steady-state metrics may include response time under specific traffic, throughput, and error rates. These metrics serve as a baseline to detect deviations during experiments. 2. **Hypothesize Potential Failure Points:** Collaborate with the team to brainstorm possible weak points in the system. Common examples include single points of failure, network bottlenecks, and third-party service dependencies. And, ask questions like: What happens if a critical service goes down? How will the system behave under high latency or packet loss? 3. **Inject Failures:** Use chaos testing tools to introduce controlled disruptions. Examples include: - Terminating a critical service instance (using tools like Chaos Monkey). - Simulating high network latency or packet drops (using tools like Gremlin). - Creating resource contention (e.g., high CPU or memory usage). - Testing infrastructure issues like DNS outages or unavailable storage volumes. 4. **Observe and Analyze Results:** Monitor system behavior and analyze logs and metrics to understand failure impact and identify areas of improvement, during the chaos test using observability tools (e.g., Grafana, Prometheus, or Datadog). The key areas to monitor, includes: - Latency: Did response times spike? - Error Rates: Were there any increased 4xx or 5xx errors? - Availability: Did the system maintain its service level objectives (SLOs)? 5. **Iterate and Improve:** Based on findings, implement changes to improve system resilience. This could include updating retry logic, adding redundancy, or improving failover mechanisms. And, retest after applying changes to validate improvements. ### Tools and Frameworks Popular tools for chaos testing include: - **Chaos Monkey:** Focuses on terminating instances in production environments. - **Gremlin:** Offers a broad set of failure injection scenarios. - **LitmusChaos:** Designed for Kubernetes environments. - **Chaos Toolkit:** An open-source platform for automating chaos experiments. - **PowerfulSeal:** Designed for cloud-native environments, particularly Kubernetes. ## What are the common pitfalls for Chaos Testing? 1. **The system can get overloaded because of** running large-scale experiments without proper safeguards. 2. Conducting chaos tests without defined goals and lack of clear objectives can lead to confusion and wasted efforts. 3. **Poor Communication and** failing to inform stakeholders about planned experiments can cause unnecessary panic. ## What are the Best Practices for Chaos Testing? 1. **Start Small by b**eginning with isolated experiments and gradually expand scope. 2. **Automate Chaos Experiments by** integrating tools and scripts into CI/CD pipelines. 3. **Collaborate Across Teams by** ensuring alignment between development, operations, and QA teams. ## Let’s understand the Case Studies ### Netflix Netflix pioneered chaos testing with Chaos Monkey, a groundbreaking tool that randomly terminates production instances to test system resilience. The tool’s inception marked the beginning of a suite of resilience tools known as the Simian Army, which includes: - **Latency Monkey:** Simulates network latency to test service timeouts and fallback mechanisms. - **Conformity Monkey:** Identifies instances that do not adhere to best practices and shuts them down. - **Chaos Gorilla:** Simulates entire availability zone outages to ensure regional failover. Netflix’s comprehensive approach to chaos engineering ensures continuous improvement in reliability across a complex, global infrastructure serving millions of users. ### Other Examples - **Twilio:** Twilio leveraged Gremlin to simulate various network disruptions, such as latency spikes and packet loss, to improve API reliability and enhance customer communication services. These tests revealed key improvements for retry logic and failover systems. - **Google:** Google’s DiRT (Disaster Recovery Testing) program goes beyond chaos testing by simulating massive-scale failures, such as data center outages, to validate global failover capabilities and disaster recovery plans. ## How Keploy Can Contribute to Chaos Testing? Keploy, primarily designed as an open-source testing platform for generating test cases and ensuring API reliability, can be a **part of a chaos** [**testing strategy**](https://keploy.io/blog/community/a-test-strategy-is-critical-for-your-project-success) when focusing on regression and behavior of API under failure scenarios. It can work alongside chaos testing tools to provide a more comprehensive resilience testing approach, especially in microservices architectures. **_But question is how?_** It can help as follows **–** - **Simulating API Failures**: [Keploy](https://keploy.io) can generate and simulate unexpected API behavior (e.g., failed responses, timeouts, or incorrect data) to test how the system handles such disruptions. This aligns with chaos testing’s goal of uncovering vulnerabilities. - **Validating System Behavior Under Stress**: By replaying API calls and testing against predefined baselines, Keploy can help ensure that the system continues to perform reliably when subjected to disruptions. - **Enhancing Observability**: It can provide insights into how APIs and microservices interact during failure scenarios, helping teams identify weak points and areas requiring redundancy. - **Automation and Integration**: By automating failure scenarios and integrating them into CI/CD pipelines, Keploy act as a step in a broader chaos testing framework. ## Conclusion Hence, by integrating chaos testing into their culture, organizations can continuously enhance robustness, improve recovery times, and maintain user trust in their systems. I hope, you were able to learn something new today, because that’s the wrap for now! If you have any more questions, you can drop it down in the comments. ## FAQs ### **How does chaos testing differ from traditional stress testing?** While stress testing examines how systems perform under extreme workloads, chaos testing focuses on unpredictable failures, such as service disruptions, network issues, or hardware failures. Chaos testing aims to uncover hidden vulnerabilities and improve resilience, even in normal workloads. ### **Can chaos testing be applied in a non-cloud environment?** Absolutely. Chaos testing is not limited to cloud-native systems. It can be used to test on-premise infrastructure, legacy systems, or hybrid environments by simulating failure scenarios like disk failures, power outages, or network disruptions. ### **What is the role of observability in chaos testing?** Observability tools like Grafana or Prometheus are crucial for monitoring system behavior during chaos experiments. They help capture real-time metrics, logs, and traces to analyze the impact of failures and verify if systems meet their reliability goals. ### **What are the ethical considerations of chaos testing in production?** Ethical chaos testing requires safeguards to minimize risks to users. Experiments must be carefully controlled, with limited blast radius, and conducted during low-traffic periods. Additionally, compliance with data privacy laws and transparency with stakeholders is essential. ### **How do you integrate chaos testing into CI/CD pipelines?** Chaos experiments can be automated and added to CI/CD workflows using tools like Gremlin or LitmusChaos. By running these tests during staging or pre-deployment, teams can ensure that new updates or configurations won’t compromise system resilience. ![Thank you graphic for chaos testing guide](https://wp.keploy.io/wp-content/uploads/2024/11/Thank-you.webp) ## Access Control Testing: Principles, Vulnerabilities & Tools URL: https://www.swapnoneel.site/blog/access-control-testing-guide Date: 2024-12-30T00:02:56.000Z Summary: Access control testing checks whether a specific user can perform a specific action on a specific resource. Test ownership, roles, direct endpoints, uploads, and side effects. Authentication answers one question: "Who are you?" Access control, also called authorization, answers the next one: "What are you allowed to do?" That distinction matters every time an API returns a record, changes an account, or accepts an uploaded file. A request can come from a correctly signed-in user and still be forbidden. If the application checks only the login and forgets the permission check, the user may be able to read another person's data or call an admin-only endpoint. This guide walks through the rules behind access control, the failures worth testing, and a practical way to turn those checks into regression tests. You can use the examples with a browser, an API client such as [Postman](https://keploy.io/blog/community/my-journey-of-automating-test-cases "Postman"), or an automated test runner. ## What access control is checking Every protected request has at least three parts: a subject, an action, and a resource. The subject might be a user or a service. The action could be reading, editing, deleting, or uploading. The resource is the thing being touched, such as an invoice, project, or user profile. The server should make the permission decision from those parts. Do not trust a role, account ID, or permission flag that arrives only in the request body. A client can change it before the request reaches your application. The useful test question is simple: if I change the identity, role, or resource ID in this request, does the server still make the right decision? ## Principles that make permissions safer ### Least privilege Give each account only the permissions it needs for its job. A support user may need to view a ticket but not delete it. A customer may edit their own profile but not another customer's profile. Least privilege reduces the damage caused by a stolen account. It also makes tests easier to reason about because every allowed action has a clear reason. ### Separation of duties Some operations should require more than one person or role. For example, the account that prepares a payment should not be the only account that can approve it. This limits what one compromised account can do by itself. ### Roles and policies Role-based access control, or RBAC, groups permissions into roles such as `admin`, `manager`, and `user`. It is useful when the rules are stable. Attribute-based access control, or ABAC, makes the decision from details such as the user, resource owner, location, or request time. The name of the model matters less than the testable rule behind it. Write down which roles can perform which actions, then test both sides of every rule. ## Common access control failures ### Horizontal privilege escalation This happens when one user can access another user at the same permission level. A classic example is changing `/user/123` to `/user/124` and receiving someone else's profile. The application authenticated you, but it failed to check that you own resource `124`. ### Vertical privilege escalation This happens when a lower-privileged user can perform a higher-privileged action. A normal user should not be able to call an admin endpoint simply by discovering its URL or copying an admin request. ### Insecure direct object references An ID in a URL is not a permission check. IDs, filenames, and document keys are often useful clues during testing because changing one may expose another record. The server must verify access to the referenced object for every request. ### Excessive permissions Sometimes the application works exactly as coded, but the role has more access than it needs. Review the permission matrix as well as the implementation. A test that passes because a user can delete every record is not a success. ### Unsafe file uploads Upload endpoints need checks for file type, size, storage location, and later execution. Try permitted and forbidden extensions in a safe test environment, and verify that a rejected upload is not stored or served as executable content. ## A practical testing workflow Start with a small permission matrix. Put roles in one column, actions in another, and record the expected response for each combination. Include ownership in the matrix when a user should access only their own records. Then capture one valid request for each protected operation. Keep the request body, path parameter, query parameter, cookie, and authorization header visible in your test notes. The permission decision can depend on any of them. ### Test horizontal access Sign in as user A and create or identify a resource owned by user B. Replay the request with user A's credentials and user B's resource ID. The server should reject it or return a response that does not disclose the protected data. For example, compare requests for `https://example.com/user/123` and `https://example.com/user/124`. A `200` response is not automatically a vulnerability, but receiving user B's private fields is a clear failure. ### Test vertical access Use a low-privilege account to call an administrative operation such as `https://example.com/admin`. Test the route directly, then test the same action through alternate HTTP methods or content types if the application supports them. Do not stop after hiding an admin button in the UI. The API must enforce the rule too. ### Test the request, not just the page Browser tests can miss authorization bugs in background requests. Inspect the API calls made by the page and change one permission-relevant value at a time. Check the status code and the response body; an error status with sensitive data in the body is still a leak. ### Test uploads and exports Try a permitted file and then a forbidden extension such as `.php` instead of `.jpg`. Check where the file is stored and whether the resulting URL can be guessed. Apply the same care to export endpoints, because a download that is hidden from the UI may still be reachable directly. ## Manual and automated checks Manual testing is useful when you are discovering the permission model. A proxy such as Burp Suite or OWASP ZAP lets you edit requests and compare the server's decisions. Postman is handy for keeping a small set of role-specific requests, and tools such as [Cypress or Playwright](https://keploy.io/blog/community/playwright-vs-cypress-choosing-the-best-e2e-testing-framework "Cypress or Playwright") can exercise complete user flows. Static analysis can find suspicious patterns in source code, such as hardcoded credentials or routes with missing middleware. Dynamic testing checks the running application, which is where configuration and service boundaries often change the outcome. Interactive analysis combines runtime behavior with code-level information. Automation pays off after you have a known-good matrix. Save the expected result for every role and action, run it in CI, and keep a regression test for every authorization bug you fix. If your service is already handling real API traffic, [Keploy's API test generator](https://keploy.io/api-testing) can capture authenticated requests and replay them as tests. Review captured tokens and redact secrets before storing the tests. ## What to verify in a test result An authorization test should verify more than a status code. Check that: - the response status matches the policy; - the body contains no fields from the protected resource; - the server does not reveal a useful difference between an existing forbidden record and a missing record, when that distinction matters; and - the denied request does not create, update, delete, or upload anything as a side effect. Also test expired tokens, missing tokens, tokens issued for another audience, and a user whose role changed after the token was issued. Those cases often expose stale permission checks. ## Keeping access rules maintainable Centralize permission decisions where practical so that different endpoints do not slowly develop different interpretations of the same role. Log denied access with enough context to investigate, but do not put passwords, raw tokens, or private request bodies in the logs. Review permissions when a feature changes. A new endpoint, background job, export button, or service-to-service call can create a second path to the same data. The UI is only one path. One caveat from working with API tests: a large number of passing requests can create false confidence if every request uses an admin token. Keep fixtures for the least-privileged roles too. They are the ones that prove the boundary exists. ## Final checks Access control is a server-side decision about a specific subject, action, and resource. Test ownership changes, role changes, direct endpoint access, alternate request shapes, and side effects. Then keep those cases in the regression suite so the permission boundary does not disappear during the next refactor. ## FAQs ### **How does Keploy assist in access control testing?** **Keploy** is an open-source testing platform that automates test generation for APIs. It captures API calls during runtime and helps validate access control policies by replaying these calls in test scenarios. Keploy’s features can identify misconfigurations in access permissions or API endpoints. ### **What’s the difference between authentication and authorization?** Authentication verifies a user’s identity (e.g., logging in with a username and password). Authorization determines what actions or resources a user is permitted to access. ### **How can DevSecOps integrate access control testing?** DevSecOps practices integrate security testing, including access control validation, into CI/CD pipelines. Tools like Keploy, OWASP ZAP, and automated test scripts can continuously verify access permissions during development. ### **How do access control mechanisms evolve with microservices architecture?** Microservices often rely on decentralized components. Access control involves: - API gateways for centralized policy enforcement. - Service-level policies (e.g., ABAC for inter-service communication). - Tools like Open Policy Agent (OPA) for flexible policy management. ### **How important is user feedback in refining access control policies?** Gathering user feedback on denied permissions or excessive restrictions helps refine access control rules, ensuring a balance between security and usability. ## Top 19 Must-Have VS Code Extensions for Developers in 2025 URL: https://www.swapnoneel.site/blog/top-5-must-use-vs-code-extensions-for-developers-in-2025 Date: 2024-12-19T22:30:33.000Z Summary: These 19 VS Code extensions cover testing, containers, Git history, formatting, HTTP requests, navigation, documentation, and AI assistance. Start with the problems you actually have. VS Code is useful on its own, but [VS Code](https://keploy.io/blog/community/how-to-run-tests-in-visual-studio-code-a-complete-guide) extensions let it meet you where you work. A frontend project needs a quick browser preview. A backend project may need HTTP requests and containers. A large repository needs better navigation and a way to see what changed. That does not mean you should install every extension you find. Each one adds another process, setting, or source of suggestions to your editor. Start with the problems you actually have, then keep the extensions that remove friction without making the workspace noisy. ## 1. Keploy for testing and debugging [Keploy](https://keploy.io/) is the testing-focused choice in this list. It supports [unit](https://keploy.io/blog/community/what-is-unit-testing), integration, and API testing across languages such as Python, JavaScript, TypeScript, Java, PHP, and Go. ![Keploy automated testing VS Code extension](https://wp.keploy.io/wp-content/uploads/2025/07/Keploys-Automated-Integration-Testing.webp) It can record and replay API requests, generate tests, and compare behavior across environments. That is useful when a service already has working traffic but not enough repeatable tests. If you are adding these checks to a [CI/CD](https://keploy.io/blog/community/how-cicd-is-changing-the-future-of-software-development) pipeline, keep the generated cases small enough to diagnose when they fail. Read them before keeping them, especially when captured requests contain private data. ## 2. Docker for container work The Docker extension brings container tasks into the editor. You can inspect images, containers, and volumes without constantly switching to another window. ![Docker extension for VS Code](https://wp.keploy.io/wp-content/uploads/2024/10/Docker-scaled-e1759610158689.png) It becomes most useful when your application already runs in Docker and you need to check logs, rebuild an image, or debug inside a container. The related [Docker comparison](https://keploy.io/blog/community/podman-vs-docker) is useful if you are still deciding which container tool belongs in your workflow. ## 3. GitLens for repository history GitLens puts authorship, blame information, file history, and branch comparisons close to the code you are reading. ![GitLens extension for Git blame and repository history](https://wp.keploy.io/wp-content/uploads/2024/12/gitlens.png) The best use is not looking up who wrote a line so you can complain about it. It is finding the change that introduced a strange condition and reading the surrounding commit. That context can prevent you from deleting a rule that looks unnecessary today. ## 4. Prettier for consistent formatting Prettier formats supported files according to a shared configuration. With format-on-save enabled, the editor applies the same layout each time you save. ![Prettier code formatter extension](https://wp.keploy.io/wp-content/uploads/2024/12/prettier.png) Formatting does not make a bug disappear, but it keeps style debates out of many code reviews. Pair it with ESLint when you want formatting and code-quality checks to have separate jobs. Let the project configuration win over your personal preference. ## 5. Live Server for a quick browser preview Live Server opens a local preview and reloads the page when you save a file. It is handy for small HTML, CSS, and JavaScript projects where setting up a full application server would be unnecessary. ![Live Server local development preview extension](https://wp.keploy.io/wp-content/uploads/2024/12/live-server.png) It will not reproduce every detail of a production deployment, so treat it as a quick feedback loop rather than a complete test environment. That distinction matters when your page depends on a backend, build step, or special headers. ## 6. ESLint for JavaScript and TypeScript checks ESLint reads JavaScript and TypeScript files against rules chosen by your project. It can catch suspicious patterns while you are typing and can report the same problems in continuous integration. ![ESLint static code analysis extension](https://wp.keploy.io/wp-content/uploads/2024/12/eslint.png) The extension is only as helpful as its configuration. Start with rules the team understands, then add stricter checks when the codebase is ready for them. If every line is covered by a warning nobody will fix, the warnings become wallpaper. ## 7. REST Client for requests inside VS Code REST Client lets you keep HTTP requests in `.http` files and run them from the editor. You can inspect JSON or XML responses without copying the request into another application. ![REST Client extension for HTTP requests](https://wp.keploy.io/wp-content/uploads/2024/12/restclient.jpg) This is useful for keeping a small, reviewable set of API examples beside the service code. Put test credentials in environment variables or a local secret store rather than committing them into the request file. ## 8. Path Intellisense for file paths Path Intellisense suggests filenames and folders as you write import statements or HTML links. ![Path Intellisense extension for file path autocompletion](https://wp.keploy.io/wp-content/uploads/2024/12/path-interllisense.png) The benefit shows up in repositories with deeply nested folders, where a path typo can take longer to find than to fix. It saves a few keystrokes, but those small savings add up when you move through a project all day. ## 9. Markdown Preview Enhanced for documentation Markdown Preview Enhanced gives you a live view of Markdown while you edit it. The listed features include syntax highlighting, diagrams, LaTeX, charts, and export options. ![Markdown All in One extension](https://wp.keploy.io/wp-content/uploads/2024/12/markdown-e1759613192640.jpg) Use it when the rendered page matters as much as the source. A heading that looks fine in plain text can wrap badly, hide a broken link, or make a table hard to scan in the preview. ## 10. GitHub Copilot for code suggestions [GitHub Copilot](https://keploy.io/blog/community/cursor-vs-github-copilot) can suggest code and text from the context around your cursor. ![GitHub Copilot AI code completion extension](https://wp.keploy.io/wp-content/uploads/2024/12/github-copilot.jpg) It is most useful for repetitive code, test scaffolding, and drafts that you already know how to review. It can also produce a confident answer that is wrong, insecure, or out of date. Read every accepted suggestion and run the tests that matter; speed is not a substitute for checking the result. ## 11. Bracket Pair Colorization for nested code Bracket Pair Colorization uses matching colors to show which opening and closing brackets belong together. ![Bracket Pair Colorization extension](https://wp.keploy.io/wp-content/uploads/2024/12/bracket.jpg) It helps when a function contains several nested objects, arrays, or callback expressions. The extension does not fix structure for you, but it makes a missing or misplaced bracket easier to spot. ## 12. IntelliCode for ranked suggestions IntelliCode changes the order and context of suggestions in IntelliSense. It is intended to help common patterns appear sooner while you type. ![IntelliCode AI-assisted code completion extension](https://wp.keploy.io/wp-content/uploads/2024/12/intellicode-ai-powered.png) Treat the suggestions as a nudge, not as a decision. If you already use another completion assistant, compare the two before keeping both enabled. More suggestions can make the editor feel busier rather than faster. ## 13. Peacock for separating workspaces Peacock changes the color of a VS Code workspace. That sounds cosmetic until you have several repositories open and nearly run a command in the wrong terminal. ![Peacock workspace color customizer extension](https://wp.keploy.io/wp-content/uploads/2024/12/peacock.png) Assign a different color to projects that are open at the same time. The color becomes a quick visual warning about which folder you are editing. ## 14. Project Manager for switching repositories Project Manager saves workspace entries so you can move between repositories without searching through folders each time. ![Project Manager extension for switching projects](https://wp.keploy.io/wp-content/uploads/2024/12/project-manager.png) It is a good fit when your day involves several codebases. Give entries names you will recognize later, and remove old projects so the list does not become another place to search. ## 15. TODO Highlight for unfinished work TODO Highlight makes markers such as `TODO` and `FIXME` visible in the editor and lets you move between them. ![TODO Highlight extension for code annotations](https://wp.keploy.io/wp-content/uploads/2024/12/todo-highlight.jpg) Use it as a reminder system, not as a substitute for tracking work. A TODO that has no owner or issue link can sit in a file for years. When a marker represents real work, give it enough context that someone can act on it. ## 16. Import Cost for dependency awareness Import Cost displays the size associated with an imported package in JavaScript and TypeScript projects. ![Import Cost extension for package bundle size](https://wp.keploy.io/wp-content/uploads/2024/12/cost-extension.jpg) The number is a prompt to investigate, not an automatic reason to remove a dependency. Check how the package is bundled and whether the import is on a user-facing path before making a change. ## 17. Settings Sync for a consistent setup Settings Sync keeps extensions, themes, and editor settings available across machines through a GitHub account. ![Settings Sync extension for VS Code configurations](https://wp.keploy.io/wp-content/uploads/2024/12/settings-sync-scaled-e1759613437823.png) Sync is convenient, but do not treat a personal settings bundle as a project requirement. Keep team rules in the repository, and check what is being synchronized before including tokens or machine-specific paths. ## 18. Code Spell Checker for names and comments Code Spell Checker catches likely spelling mistakes in identifiers, comments, and documentation. ![Code Spell Checker extension](https://wp.keploy.io/wp-content/uploads/2024/12/Code-Spell-Checker.webp) It is especially helpful when a misspelled variable name has already spread across several files. Add project-specific words to its dictionary rather than ignoring every warning. ## 19. Code Time for activity patterns Code Time records coding activity and presents trends or goals. Some people like seeing how their working sessions change over time; others find the numbers distracting. ![Code Time extension for developer productivity metrics](https://wp.keploy.io/wp-content/uploads/2024/12/code-time.png) My caveat is that time in the editor is not the same thing as useful work. Use these metrics as a personal signal if they help you notice a habit, but do not turn them into a scoreboard for yourself or a team. ## A smaller starting set You do not need all 19 extensions on day one. For a new web project, I would start with Prettier, ESLint, Live Server, and REST Client. Add Docker when the project uses containers, GitLens when repository history becomes important, and a testing extension when you have a repeatable test workflow to support. Also, watch the editor after installing anything new. If startup gets slow or suggestions become noisy, disable extensions one at a time and keep the ones that solve a real problem. The best setup is the one you can explain, maintain, and still enjoy using. ## Common setup questions Open the Extensions view in VS Code, search for an extension, and select Install. Most of the extensions in this list have a free path, but check the extension's own listing for current licensing and optional paid features. Keep extensions updated when the changes fit your project, and disable ones you no longer use. If an AI extension suggests code, review it for correctness, security, and fit with your repository before committing it. ## FAQs ### 1. How do I install VS Code extensions? Open the Extensions view in VS Code, search for your desired extension, and click "Install." ### 2. Are these extensions free? Most are free, though some, like extension, have premium tiers for advanced features. ### 3. Can I use these extensions on other editors? Many are available on other editors, but VS Code integrations provide the best experience. ### 4. Will too many extensions slow down VS Code? Yes, disable unused extensions and monitor performance to keep VS Code running smoothly. ### 5. How do I keep my extensions updated? Enable auto-update in VS Code settings or manually update via the Extensions view. ### 6. Which extensions are best for beginners? Start with Prettier, ESLint, Live Server, and Keploy they’re easy to use and boost productivity immediately. ### 7. Can I use AI extensions safely in professional projects? Yes, AI suggestions should be reviewed, but extensions like IntelliCode can greatly speed up coding tasks. ## Unit Testing vs Integration Testing: A Comprehensive Guide URL: https://www.swapnoneel.site/blog/unit-testing-vs-integration-testing-a-comprehensive-guide Date: 2024-12-17T23:48:38.000Z Summary: Unit tests check one piece of logic; integration tests check how pieces work together. This guide compares their scope, speed, use cases, tools, and a practical testing plan. When you change software, you want an answer to a simple question: did this change break the behavior that was already working? [Software testing](https://keploy.io/blog/community/software-testing-basics) gives you that evidence, while unit tests and integration tests answer the question at different distances. A unit test stays close to one piece of logic. An integration test crosses a boundary between pieces, such as an application and a database. You need both when the risk lives in the code and in the connections around it. ## What unit testing checks [Unit testing](https://keploy.io/blog/community/what-is-unit-testing) exercises a small unit in isolation. A unit is often a function, method, or class, but the useful definition is smaller: it is the piece you can call and check without starting the rest of the application. ![Unit testing process overview](https://wp.keploy.io/wp-content/uploads/2024/12/What-Is-Unit-Testing.webp) The test supplies an input and checks the output or the observable effect. Dependencies such as databases and network clients are usually replaced with fakes, stubs, or mocks so the test stays focused on the unit's own decision. That isolation makes unit tests fast and easier to debug. It also makes them cheap to run while you are editing code. The limitation is just as important: a passing unit test does not prove that the unit is wired correctly to a real database or service. ### A small unit test This example can run with a Python test runner such as PyTest: ```python def add_numbers(a, b): return a + b def test_add_numbers(): assert add_numbers(2, 3) == 5 assert add_numbers(-1, 1) == 0 ``` The test checks two behaviors: ordinary addition and a result that returns to zero. If it fails, you have a short function and a short list of places to inspect. ## What integration testing checks [Integration testing](https://keploy.io/blog/community/integration-testing-a-comprehensive-guide) checks whether separate parts of the application work together. That might mean calling an HTTP route that reads from a database, sending a message to a queue, or passing a value from one module to another. ![Integration testing process overview](https://wp.keploy.io/wp-content/uploads/2024/12/What-Is-Integration-Testing.webp) Because the test crosses a boundary, it needs more setup. You may create a test database, load known data, start a service, or configure a client. The test takes longer than a unit test, but it can catch problems that isolation hides: a wrong column name, a mismatched JSON field, an incorrect serializer, or a connection setting that only fails outside the mock. ### A runnable integration test This small example uses an in-memory SQLite database from Python's standard library. It creates the table, inserts a record, and reads it through the function under test, so it can run without a separate database server. ```python import sqlite3 def get_user(connection, user_id): row = connection.execute( "SELECT name, email FROM users WHERE id = ?", (user_id,), ).fetchone() return row def test_get_user_from_database(): connection = sqlite3.connect(":memory:") connection.execute( "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)" ) connection.execute( "INSERT INTO users (name, email) VALUES (?, ?)", ("John Doe", "johndoe@example.com"), ) connection.commit() user = get_user(connection, 1) assert user == ("John Doe", "johndoe@example.com") connection.close() ``` This test is still small, but it checks the application code against a real database engine. It does not prove that production uses the same configuration, so a larger system may also need tests against its actual database setup. ## How the two types differ Think of unit testing as checking a single conversation and integration testing as checking that the conversation reaches the right person. A unit test can prove that a formatter returns the expected string. An integration test can prove that the route calls the formatter, stores the result, and returns the expected response. Unit tests usually use mocked or in-memory dependencies and finish quickly. Integration tests use more realistic dependencies, need setup and cleanup, and take longer. Unit failures tend to point near the defect; integration failures can require you to inspect the boundary and both sides of it. The difference is about scope, not importance. A unit test is not a cheaper version of an integration test, and an integration test is not automatically a better test. Each one protects a different promise. ## When to write each test Write a unit test when the behavior belongs to one function or module. Calculations, parsing rules, validation, and branching logic are good candidates. Run these tests on every change so they give you quick feedback while the code is fresh in your mind. Write an integration test when the risk is in the connection. Use one for an API and its database, a service and its message broker, or a repository and the storage system it actually uses. Focus on the boundaries that would be expensive to discover through manual debugging. Do not wait until every unit test passes before thinking about integration tests. A unit test can guide local design, while a small integration test can reveal early that the chosen interface does not fit the real dependency. ## Practices that keep tests useful For unit tests, keep the Arrange-Act-Assert shape visible. Arrange the inputs and substitutes, call the unit once, and assert the behavior that matters. Keep each test focused enough that a failure explains what changed. Mock only the dependencies that need isolation. If you mock every object in the call chain, the test may pass while the real objects disagree about a method name or data shape. That is a good signal to add an integration test instead. For integration tests, use an isolated environment and deterministic data. Create the records you need, clean them up after the test, and do not let one test depend on leftovers from another. Add failure cases such as an unavailable service, a timeout, or a missing record when those failures matter to the application. Keep the test setup close to the behavior it protects. Shared fixtures are convenient, but a huge fixture can hide why a test needs a particular record. A little repetition is often easier to maintain than invisible global state. ## Tools and Keploy's place JUnit for Java, PyTest for Python, and [Jest for JavaScript](https://keploy.io/blog/community/migrate-from-jest-to-vitest) are common choices for unit tests. This [JUnit comparison](https://keploy.io/blog/community/testng-vs-junit-performance-ease-of-use-and-flexibility-compared) can help when you are choosing a Java test framework. For integration and API checks, tools such as Postman, Selenium, and Testcontainers can help you exercise the boundary with more realistic dependencies. The original article also highlights Keploy for API integration testing. Its approach records API traffic and turns those interactions into reusable test cases. That can save manual setup when an API already has representative traffic, but you should still remove sensitive values and review each expected response. Keploy can be useful around API boundaries, but it does not remove the need for small unit tests. A captured request can tell you that an endpoint behaves a certain way. It cannot replace a focused test for every calculation inside the endpoint. My caveat is that I would not choose a tool before choosing the behavior I need to protect. A plain test in the framework your team already runs is often a better starting point than a new dashboard full of tests nobody understands. ## A practical testing plan Pick one important workflow. Cover its local rules with unit tests, then add one integration test that crosses its most valuable boundary. Run both in the normal development and CI checks, and add an end-to-end test only when the full user journey adds information that the lower layers cannot provide. When a test fails, first ask which promise it was meant to protect. If the failure message cannot answer that, improve the test before adding more cases. A smaller suite with clear failures will help you more than a large suite that only tells you that something somewhere is red. ## FAQ ### **What is the main difference between unit testing and integration testing?** Unit testing focuses on testing individual components of the application in isolation, whereas integration testing validates the interactions between multiple components to ensure they work together as expected. ### **Why is unit testing faster than integration testing?** Unit tests operate in isolation, often using mocks or stubs for dependencies, which eliminates external system overhead. Integration tests involve real systems like databases or APIs, which increase execution time due to setup and network dependencies. ### **Can unit testing replace integration testing?** No, unit testing cannot replace integration testing. Unit tests verify the correctness of individual components, while integration tests ensure that these components work seamlessly when combined. Both are necessary for robust software testing. ### **How does Keploy assist in integration testing?** Keploy is an open-source platform that simplifies integration testing by automatically generating test cases from API interactions. It reduces the manual effort involved in writing integration tests and ensures seamless validation of API behavior. ### **Should integration tests include real systems or mocks?** Integration tests are most effective when they include real systems, as this mimics actual usage scenarios. However, in certain cases, lightweight mocks may be used to simulate unavailable external systems during testing. ### **How can I ensure integration tests are reliable?** To ensure reliability, use isolated test environments, automate the setup and teardown process, and simulate realistic scenarios. Tools like Keploy can help generate and maintain high-quality integration test cases. ![Thank you graphic for testing guide](https://wp.keploy.io/wp-content/uploads/2024/11/Thank-you.webp) ## Comparing GitHub Copilot vs. ChatGPT for Unit Testing URL: https://www.swapnoneel.site/blog/comparing-github-copilot-vs-chatgpt-for-unit-testing Date: 2024-12-06T00:13:24.000Z Summary: GitHub Copilot works beside your code, while ChatGPT is better for conversation and reasoning around a test. Compare both on a real unit-testing task and see where each falls short. ChatGPT and GitHub Copilot can both write a unit test. That does not make them interchangeable. The difference shows up when the test needs context. Copilot works beside the file you are editing and can suggest a test from nearby code. ChatGPT gives you a conversation, which is better when you need to explain a failure, compare approaches, or reason through a missing case. I would not ask either tool to "write comprehensive tests" and paste the answer into a repository. A test can be syntactically correct, run green, and still assert almost nothing. The interesting comparison is how much work each tool leaves you before the test describes the behavior you actually care about. ## The two tools in plain English ChatGPT is a general conversational model. You provide code, an error, a requirement, or a question, and it responds with an explanation or a possible implementation. It is useful when you want to keep asking "why?" until the behavior makes sense. OpenAI releases newer models over time. The [impact of GPT-o3-mini on tech](https://keploy.io/blog/community/impact-of-gpt-03-mini-on-tech) is one example of how quickly the available options change, so treat a model name as a detail of the setup rather than the whole workflow. GitHub Copilot is an editor assistant. It uses the file around your cursor and other available project context to suggest code, complete a line, or draft a function. You stay in the editor, which makes it quick for small changes and repetitive test setup. ![ChatGPT Plus vs GitHub Copilot comparison](https://cdn.mos.cms.futurecdn.net/9HNs2rcSFyJepccD2sx2uk.jpg) ## A unit-testing task worth comparing Suppose you have a function that calculates a shipping charge. The happy path is easy: pass a valid order and check the amount. The useful tests are the ones around it: an empty order, an invalid address, a free-shipping threshold, and a service failure. Copilot may suggest the test file and infer the imports from the repository. That saves typing. It may also copy the implementation's assumptions so closely that the tests all repeat the same mistake. ChatGPT may give you a longer list of cases and explain why each one matters. You have to paste the relevant code and requirements, though, and the answer can drift away from your project's test framework or fixtures. That tradeoff comes up again and again: Copilot is close to the code, while ChatGPT is better at a back-and-forth discussion. ## Where ChatGPT is stronger ChatGPT is the better partner when the requirement is still fuzzy. You can describe the business rule, show a failing assertion, and ask it to separate the observable behavior from the implementation details. It is also useful for debugging a test that fails for a reason you do not understand. Ask it to trace the inputs, expected output, mocks, and side effects in order. The explanation is often more valuable than the replacement code. You can use the same conversation for test naming, fixture design, documentation, or an alternative implementation. That range is convenient when the problem is larger than one line in one file. The cost is context management. ChatGPT cannot safely infer your whole repository from a small pasted snippet. If you omit a fixture, environment variable, or dependency version, the answer may be polished and irrelevant. ## Where Copilot is stronger Copilot wins when you already know the test you want and need to write it. It can follow the existing imports, naming conventions, and nearby patterns. That makes it good at table-driven tests, mock setup, and the next case in a test file. It also keeps the feedback loop short. You write the assertion, inspect the suggestion, run the test, and correct it in the same place. For repetitive work, that is a real advantage. The weak spot is explanation. Copilot can produce a convincing test without telling you which requirement the test covers or which important case is missing. It may also generate generic assertions because the code around the cursor does not contain the product context. ## What both tools get wrong Neither tool knows whether your test is worth having unless you give it the behavior. Both can guess the wrong return value, mock the wrong boundary, or test a private helper instead of the public behavior that users depend on. They can also create brittle tests. A test that checks the exact order of internal calls may fail during a harmless refactor, while a test that checks only that no exception was raised may miss a broken result. Review generated tests for four things: the input that triggers the behavior, the output that proves it, the side effects that must not happen, and the failure path. If you cannot state what bug the test would catch, keep working on the test before asking a tool for more of them. ## Using them together There is a sensible combined workflow. Use ChatGPT to turn a requirement into a list of observable cases and to explain a tricky failure. Use Copilot to place those cases into the repository's existing test structure. Then run the tests yourself and delete anything that does not protect behavior. The two tools can speed up separate parts of the work, but neither one should decide that a green test suite means the feature is correct. ## Other tools worth considering Cursor IDE combines an editor with AI-assisted completion and refactoring. That may appeal to you if you want the conversation closer to the codebase. CodeAnt AI focuses more on code quality, best-practice checks, and security analysis. It belongs in a review workflow rather than being treated as a replacement for a unit-test design. For API behavior, a capture-based tool such as Keploy can fill a gap that code assistants often miss. Instead of guessing requests from a function, it records real application interactions and replays them as tests. That is a different job from drafting a unit test, but it can protect boundaries between services. ## My pick for unit testing If I am learning a codebase or trying to understand why a test should exist, I pick ChatGPT. If I already know the case and want to write it inside a familiar test file, I pick Copilot. For a team choosing one tool specifically for unit-test authoring, Copilot gets my vote because the editor context removes copy-and-paste work. ChatGPT is the one I would keep nearby for reasoning and debugging, so the strongest setup is often both when your budget and data policy allow it. But that's just me, and your workflow might be different. Start with one small feature, inspect every generated assertion, and keep only the tests that would catch a real regression. ## A note on generated test coverage Coverage numbers can rise while confidence stays flat. A generated test suite may execute many lines without checking the decision that matters to a user. Keploy's [unit test generator](https://keploy.io/blog/technology/revolutionising-unit-test-generation-with-llms) takes a code-semantic approach to drafting cases. It can reduce the manual setup, but you still need to review the resulting tests and remove cases that do not match the contract of your code. The hard part of unit testing is not producing more files. It is choosing assertions that make a future failure obvious. ## FAQs ### **Can I use GitHub Copilot and ChatGPT together to improve my coding productivity?** Yes, many developers find that using both tools together enhances their productivity. For example, you can use GitHub Copilot to generate code quickly within your IDE, while relying on ChatGPT for deeper explanations, debugging, and exploring alternative approaches. Combining both tools allows for a well-rounded coding experience that covers quick implementations and detailed context. ### **Does ChatGPT support collaboration within development teams?** While ChatGPT itself does not provide built-in team collaboration features, it can help your team improve by offering code reviews, architecture discussions, and exploring best practices through interactive conversations. For collaborative workflows and testing, tools like Keploy can enhance team productivity, especially by ensuring the reliability of APIs and minimizing regression issues. ### **Can ChatGPT or Copilot help with API testing similar to what Keploy offers?** ChatGPT and Copilot can assist in generating code for API tests, but they do not offer the specialized capabilities of Keploy, such as automated test generation, mocking, and seamless regression testing. Keploy focuses specifically on API reliability and robustness, making it a specialized choice for comprehensive API testing compared to the broader code generation capabilities of Copilot and ChatGPT. ### **How do GitHub Copilot and ChatGPT handle code security and sensitive data?** Both tools require careful usage around sensitive data. GitHub Copilot and ChatGPT are trained on large datasets, and they can sometimes make insecure code suggestions. Tools like Keploy, when integrated with your development process, can further ensure API testing covers potential vulnerabilities and edge cases to improve overall software quality. ## How AI code is Transforming the Future of Software Development URL: https://www.swapnoneel.site/blog/ai-code Date: 2024-11-29T02:05:01.000Z Summary: AI-assisted code can handle boilerplate, explanations, tests, and refactors, but it can also produce convincing mistakes. This guide shows where to use it and how to review the result. AI-assisted code is software written with help from a model that predicts text from a prompt or the code around it. Sometimes the model completes one line. Sometimes it drafts a function, a test, or a whole file. That sounds close to having another programmer beside you, but the comparison has a sharp limit. The model does not own the problem, know your product rules, or feel the consequences of a bad change. You still have to decide what the code should do and check whether it actually does it. ## What people mean by AI code There are two common meanings. The first is code generated by an AI system from a natural-language request. The second is ordinary code that an AI tool has explained, refactored, tested, or reviewed. Tools such as GitHub Copilot, ChatGPT, and TabNine can help with both. A code editor assistant can suggest the next few lines while you type. A conversational tool can inspect a pasted function, explain an error, or propose a design. The interface changes the workflow, but the basic risk stays the same: a plausible answer can still be wrong. ![AI tools for solving coding problems](https://wp.keploy.io/wp-content/uploads/2024/11/AI-For-Solving-Coding-Problems.webp) ## Where AI helps with code ### Drafting repetitive code Ask for a small function with clear inputs and outputs, and an assistant can produce a first draft quickly. It is particularly useful for boilerplate such as serializers, configuration objects, command-line parsers, and test scaffolding. The useful phrase here is "first draft." Read the result before you keep it. Check the error path, the types, and the assumptions the prompt left unstated. ### Explaining unfamiliar code When you inherit a large function, ask the tool to describe its inputs, side effects, and failure cases. You can then ask about one branch at a time. This is often more useful than asking for a full rewrite because the explanation gives you a chance to notice a wrong assumption. ### Finding bugs and security mistakes An assistant can point out an unhandled `None`, a missing boundary check, or a query that builds SQL from raw input. Static-analysis products such as DeepCode and Snyk also use machine learning to find patterns associated with bugs and security problems. Treat those findings as leads. A model may warn about code that is safe in your context, and it may miss a problem that depends on configuration or data. Run a test and inspect the surrounding code before changing anything. ### Refactoring AI is handy for mechanical changes: renaming a variable across a file, converting a repeated block into a helper, or moving a function while keeping imports consistent. Give it a narrow change and a clear constraint. Broad requests such as "make this better" usually produce code that looks different without solving a specific problem. ### Writing tests An assistant can draft a test from a function signature. You still need to supply the behavior that matters. Ask for the normal case, a boundary case, invalid input, and the expected side effect. Then read the assertions closely. A test that only checks that a function returns a value can pass while the function is completely wrong. Tools such as Keploy take a different route by capturing real application requests and responses, then turning those interactions into tests. That can give you cases that a prompt would not guess, especially around API behavior. ### Teaching and learning If you are learning Python, JavaScript, or another language, ask for a small example and then change one part of it yourself. Ask why the change affects the output. This turns the assistant into a patient explanation tool instead of a copy machine. Do not skip the uncomfortable part of learning. If you accept every answer without tracing it, you may finish a task while learning almost nothing about the language. ## What you gain and what you do not The obvious gain is time on repetitive work. You can get past an empty file, generate a rough test, or translate a small pattern between languages without searching through several examples. You also get a second way to phrase a problem. That is useful when you know the outcome you want but cannot yet see the implementation. The model may suggest an approach you can evaluate, even when you do not use its code. But faster typing is not the same as faster delivery. If the generated code adds a hidden bug, you have moved time from writing to debugging. That trade can be worth it for a small helper and awful for authentication, billing, permissions, or data migration code. My own rule is deliberately boring: I will accept a generated snippet only when I can explain why it works and point to a test that would fail if it did not. That rules out a lot of impressive-looking output, and I am fine with that. ## Risks you need to handle ### Incorrect but convincing output Language models predict likely code; they do not run your application in their head. They can invent an API, use an outdated method name, or quietly change a requirement. Compilation catches some mistakes. It does not catch a wrong business rule. ### Missing project context The assistant may not know your database constraints, coding conventions, deployment environment, or the reason a strange-looking line exists. Include the smallest useful context, and never paste secrets, private keys, customer data, or tokens into a tool that your project has not approved. ### Security and licensing questions Generated code can contain an insecure default or a dependency you did not plan to maintain. Review new packages and run your normal security checks. Your organization may also have rules about sending source code to external services or accepting generated material, so check those rules before adopting the tool. ### Losing the ability to review If an assistant writes code faster than you can read it, the workflow has become unsafe. Keep changes small. Ask for a diff or one function at a time. Run tests after each meaningful change rather than collecting a large pile of suggestions and hoping the final build tells the truth. ## A safer way to use an AI coding assistant Start with a written requirement. Include the input, output, constraints, and one example. Ask for a plan before asking for code when the task is larger than a single function. Then review the result in this order: - Does it solve the stated problem rather than a nearby one? - What happens with empty, invalid, or unusually large input? - Does it change state, call a service, or expose data in a way you did not request? - Can you test the important behavior? Keep the generated change behind the same review, linting, type checks, and tests as any other change. AI code does not get a special path through the repository. ## Where this is heading The most useful assistants will get better at working with the files, tests, and tools that already define a project. That could reduce the amount of context you need to repeat in every prompt. It will not remove the need for judgment. A tool can propose a fix for a failing test, but you still decide whether the test describes the right behavior. It can explain a legacy module, but you still decide whether a rewrite is safe. The person responsible for the result remains the person who merges it. ## The short version AI code is useful when it removes typing and helps you think. It is risky when it replaces understanding. Use it for drafts, explanations, mechanical refactors, and test ideas. Keep the specification, review, and final decision with you. There is no shame in rejecting a suggestion that looks clever but cannot be verified. That is usually the more professional choice. ## FAQ’s ### Can AI generate test cases for my application? Yes! AI can automate the generation of test cases by analyzing your codebase and identifying potential scenarios to test. Tools like **Keploy** take this a step further by automatically capturing real-world scenarios as test cases during runtime. This ensures comprehensive coverage and helps simulate realistic conditions for robust software testing. ### How does AI handle legacy codebases? AI tools can assist in understanding, modernizing, and optimizing legacy codebases by analyzing patterns, detecting outdated code, and even suggesting updates to make them compatible with modern standards. Some advanced tools can also generate tests for legacy code to ensure reliability during refactoring. ### Is AI-generated code reliable for production use? AI-generated code is a great starting point, but it still requires human review to ensure reliability, security, and adherence to project-specific standards. Platforms like **Keploy** help improve reliability by generating tests to validate the AI-generated code in various scenarios. ### Will AI replace the need for human QA engineers? No. While AI can automate repetitive testing tasks and generate test cases, human QA engineers bring critical thinking, creativity, and domain expertise to ensure the software meets user expectations. AI acts as an augmentation tool, not a replacement. ### How do AI tools like Keploy integrate with existing workflows? Keploy and similar tools integrate effortlessly into CI/CD pipelines, capturing test cases during normal development and deployment processes. This ensures minimal disruption to your workflow while enhancing the quality of your software. ![Thank you graphic for AI coding blog](https://wp.keploy.io/wp-content/uploads/2024/11/Thank-you.webp) ## Find Elements in a Python List: 7 Methods with Code Examples URL: https://www.swapnoneel.site/blog/guide-finding-elements-in-a-list-using-python Date: 2024-11-18T00:46:33.000Z Summary: Python offers several ways to find values in a list, from in and index() to comprehensions, filter(), any(), and all(). Choose by the question you need to answer. Python lists keep items in order, and each item has a position called an index. You will often need to check whether a value is present, find its position, or collect every value that matches a rule. The right method depends on the question. Use `in` for a yes-or-no membership check. Use `index()` for the first position. Use a comprehension or `filter()` when you want a new collection. If you need repeated membership checks, a `set` may be a better data structure. You can also read more about related Python control flow in [https://keploy.io/blog/community/python-switch-case-how-to-implement](https://keploy.io/blog/community/python-switch-case-how-to-implement). ## Check membership with in The `in` operator returns `True` when Python finds the value in the list and `False` when it does not. It is the clearest option when you do not need the position. ```python my_list = [10, 20, 30, 40, 50] print(20 in my_list) # True print(100 in my_list) # False ``` For a list, Python checks items from left to right until it finds a match. That means the worst-case time is O(n), where n is the number of items. It is usually fine for a small list or a one-off check. ## Find the first position with index() `list.index(value)` returns the index of the first matching item. Python uses zero-based indexing, so the first item is at position `0`. ```python my_list = [1, 2, 3, 4, 2, 5] print(my_list.index(2)) # 1 ``` If the value is missing, `index()` raises `ValueError`. Check membership first when a missing value is expected to be normal, or handle the exception when you want to keep the lookup in one place. ```python value = 6 if value in my_list: print(my_list.index(value)) else: print("Element not found.") ``` This performs two searches when the value is present. For a small list that does not matter. If you are doing this repeatedly, use a loop or another data structure instead. ## Find every matching index `index()` stops at the first match. Use `enumerate()` inside a list comprehension when you need every position where the value appears. ```python my_list = [1, 2, 3, 4, 2, 5, 2] indices = [index for index, value in enumerate(my_list) if value == 2] print(indices) # [1, 4, 6] ``` `enumerate()` gives you the current index and value together. This is useful when the position matters, such as when you need to update, report, or remove matching entries. See [what `enumerate()` means in Python](https://keploy.io/blog/community/what-does-enumerate-mean-in-python) for another example. ## Filter by a condition Sometimes you are not looking for one exact value. You may want every number above a limit, every filename with a suffix, or every record that meets a business rule. `filter()` accepts a function and an iterable. It returns an iterator, so wrap it in `list()` when you need to print or reuse all of the results immediately. ```python my_list = [5, 10, 15, 20, 25] result = list(filter(lambda number: number > 15, my_list)) print(result) # [20, 25] ``` The lambda works here, but a named function is easier to read when the condition grows. ```python def is_large(number): return number > 15 result = list(filter(is_large, my_list)) print(result) # [20, 25] ``` ## Use a list comprehension A list comprehension is often the most readable choice for a new list based on a condition. It keeps the loop and the filter in one expression without hiding the result behind an iterator. ```python my_list = [1, 2, 3, 4, 5, 6, 7] even_numbers = [number for number in my_list if number % 2 == 0] print(even_numbers) # [2, 4, 6] ``` Use a normal `for` loop instead when the body needs several steps or side effects. A compact expression is not automatically clearer. ## Find the smallest and largest value The built-in `min()` and `max()` functions scan an iterable and return its smallest and largest item. ```python my_list = [100, 45, 78, 23, 56] print(min(my_list)) # 23 print(max(my_list)) # 100 ``` Both functions raise `ValueError` for an empty list. If an empty list is possible, check it first or provide a value that makes sense for your application. ```python my_list = [] if my_list: print(min(my_list)) else: print("The list is empty.") ``` ## Check whether values are truthy with any() and all() `any()` returns `True` when at least one item in the iterable is truthy. `all()` returns `True` only when every item is truthy. ```python my_list = [0, 1, 2, 3] print(any(my_list)) # True: 1, 2, and 3 are truthy print(all(my_list)) # False: 0 is falsy ``` These functions are most useful with a condition rather than raw numbers. ```python scores = [72, 81, 94] print(any(score < 50 for score in scores)) # False print(all(score >= 50 for score in scores)) # True ``` Python stops as soon as the answer is known. `any()` stops at the first truthy value, and `all()` stops at the first falsy value. ## Choose a set for repeated lookups Lists preserve order and allow duplicates. A set removes duplicates and is designed for membership checks. Building the set costs time and memory, but later lookups are average O(1) instead of scanning the list each time. ```python names = ["Mira", "Dev", "Mira", "Sam"] name_set = set(names) print("Sam" in name_set) # True print("Alex" in name_set) # False ``` Use a list when order or duplicates matter. Use a set when your main question is whether a value exists. A set cannot contain unhashable values such as lists, so that choice also depends on the kind of data you have. ## A quick decision guide Ask yourself what the result should be: - Need `True` or `False`? Use `in`, `any()`, or `all()`. - Need the first position? Use `index()` and handle a missing value. - Need every position? Use `enumerate()`. - Need a new list from a rule? Use a comprehension or `filter()`. - Need the smallest or largest item? Use `min()` or `max()`. - Need many membership checks? Consider a `set`. You do not need to memorize every method. Start by naming the result you want, then choose the expression that says it plainly. ## Further readings [https://keploy.io/blog/community/pull-api-data-python](https://keploy.io/blog/community/pull-api-data-python) [https://keploy.io/blog/community/when-to-use-a-list-comprehension-in-python](https://keploy.io/blog/community/when-to-use-a-list-comprehension-in-python) [https://keploy.io/blog/community/introduction-to-gitlab-python-api](https://keploy.io/blog/community/introduction-to-gitlab-python-api) ## FAQs ### **How can I find the last occurrence of an element in a list?** You can use `list.reverse()` to temporarily reverse the list, then use `index()`. Alternatively, use list slicing with `len(my_list) - 1 - my_list[::-1].index(value)` for an efficient approach. ### **Can I find elements using regular expressions in lists of strings?** Yes, you can use the `re` module for this. Iterate through the list and apply the regex search condition to filter matches. ### **Can I find elements based on complex conditions?** Yes, list comprehensions with multiple conditions or functions like `filter()` can be used to create flexible searches, even with custom logic. ### **How do I find elements in nested lists?** Use recursion or flatten the list with itertools’ `chain()` or custom functions. It involves traversing each level of nested lists to search for elements. ### **What is the performance of finding elements in a list?** Searching with `in` or `list.index()` has a time complexity of O(n) in the worst case. For faster searches, consider using sets or dictionaries, which have average O(1) lookup time. ![Thank you graphic for Python list blog](https://wp.keploy.io/wp-content/uploads/2024/11/Thank-you.webp) ## The Impact of AI on Code Commenting and Software Documentation URL: https://www.swapnoneel.site/blog/the-impact-of-ai-on-code-commenting-and-software-documentation Date: 2024-11-15T00:21:42.000Z Summary: AI can draft comments and documentation, but it cannot decide whether they explain the right behavior or the reason behind it. Use a review loop before committing generated text. Comments and documentation answer questions that the code cannot answer by itself. A function can show how it calculates a value, but it may not show why the product needs that rule, which input is trusted, or what must stay true when the code changes. That information is easy to postpone. Then a few months pass, the original author is busy, and a harmless-looking change turns into archaeology. AI tools can help you write a first draft, but they cannot take responsibility for whether that draft describes the code honestly. ## What good documentation is supposed to do Start with the reader. Someone opening a file should be able to understand the purpose of the module, the assumptions around its inputs, and the unusual decisions that would otherwise look like mistakes. Comments are most useful when they explain a reason or a constraint. They should not narrate obvious syntax. This comment adds little information: ```python # Add one to the count. count += 1 ``` This one gives the next reader something they could not get by staring at the line: ```python # Keep the first event in the count because the reporting API uses one-based totals. count += 1 ``` Documentation outside the code has a different job. A README can explain how to run a project. An API document can describe a request and response. A comment belongs near the decision it explains, so it should stay short enough to update when that decision changes. ## Where AI helps AI assistants are useful when the work is repetitive and the boundaries are clear. They can summarize a file, suggest a docstring, or turn a function signature into a rough explanation. Tools such as GitHub Copilot and Amazon CodeWhisperer can also propose comments while you write code. For example, an assistant may draft this explanation for a recursive factorial function: ```python # This function calculates the factorial of a given integer. # It uses recursion to find the product of all positive integers up to n. def factorial(n): if n <= 1: return 1 return n * factorial(n - 1) ``` The draft is a starting point, not a completed review. It does not say what should happen for a negative value, and it assumes that recursion is the right detail for the reader. You still need to decide whether the comment belongs, what contract the function should have, and how that contract is enforced. AI can also summarize a long file before you read it closely. That can help you find the main entry points, but a summary is a map, not proof. Check it against the code before you copy it into a README or an issue. ## When a generated comment is dangerous The code tells the model what exists. It does not always tell the model why it exists. A generated sentence can sound confident while inventing an intention, missing an edge case, or describing an old version of the implementation. That matters most around permissions, money, security checks, retries, data retention, and compatibility rules. A wrong comment in one of those places can push the next person toward the wrong fix. There is another failure mode: stale truth. If a comment repeats what the next line does and that line changes, the comment can drift without anyone noticing. The reader then has two conflicting versions of the program. My caveat is simple: I would rather leave a small section undocumented for a moment than merge a polished explanation that nobody verified. A plain TODO that names the missing decision is more honest than an incorrect paragraph. ## A review loop that works Use the generated text in a short loop. First, ask the tool for a draft that focuses on behavior and assumptions. Then read the code yourself and delete anything that merely repeats syntax. Finally, run the tests and update the comment if the test exposes a different contract. Keep the prompt close to the question you need answered. "Explain this file" is broad. "Describe why this cache entry is rejected when the version changes" gives the tool a narrower task and gives you a clearer result to review. Do not send secrets, private data, or proprietary implementation details to a service unless your project allows it. Also check generated comments for sensitive names, internal URLs, and details that should not be public. Documentation can leak information even when the code path itself is protected. ## Can AI help with software testing too The same review rule applies to generated tests. A test is useful when it checks a behavior you care about, not when it only makes the coverage number larger. [Keploy](https://keploy.io) is mentioned here because it can generate test cases and stubs or mocks for unit and integration testing from API interactions. If you use a tool like that, inspect the captured inputs, remove sensitive values, and confirm that the expected response represents a real contract before keeping the test. ![Keploy documentation logo](https://keploy.io/docs/img/keploy-logo-dark.svg) The image above belongs in the testing section because the connection is practical: documentation tells you what a boundary should do, and a test can check that the boundary keeps doing it. The tool can save typing, but you still own the test's meaning. ## The part AI cannot sign off on AI is good at producing a plausible first pass. You are still responsible for the final sentence. Check every claim against the code, the tests, and the project rules, then leave a comment only when it will help the next reader make a better decision. That is the useful division of labor. Let the tool handle the blank page and repetitive wording. Keep the judgment, context, and security review with the person who understands what the software is allowed to do. For more writing about software, follow me on [Twitter (swapnoneel123)](http://twitter.com/swapnoneel123). You can also browse my [GitHub (Swpn0neel)](https://github.com/Swpn0neel) projects. ![Thank you graphic for AI code commenting blog](https://wp.keploy.io/wp-content/uploads/2024/11/Thank-you.webp) --- ## FAQ’s ### Can AI-generated documentation fully replace human effort? No, AI-generated documentation should complement, not replace, human effort. While AI tools provide a great starting point, developers are needed to review, refine, and ensure that comments accurately reflect the code’s purpose. Human oversight ensures clarity, avoids inaccuracies, and accounts for context and project-specific nuances that AI might miss. ### Does using AI for documentation affect project security? Potentially, yes. AI tools may inadvertently generate comments that expose sensitive logic or highlight potential weaknesses in the code. It’s essential to review all AI-generated comments for security implications and ensure they align with the project’s security protocols. ### Are AI documentation tools customizable for specific project needs? Yes, many AI-powered documentation tools offer some degree of customization. Developers can configure rules for comment styles, preferred templates, or even teach the model about specific code structures common to the project or organization. This ensures more accurate, tailored output. ### Can AI-generated documentation be used as a learning tool? Absolutely! For newer developers or team members, AI-generated documentation can serve as a useful starting point to understand unfamiliar codebases. It can provide immediate context and overviews, aiding faster comprehension. Paired with tools like Keploy for testing, new members can experiment and learn how different parts of the system interact. ### How can Keploy help enhance automation in software development? Keploy, known for its focus on test generation and automation, can be a complementary tool when used with AI-driven documentation tools. By generating test cases automatically, it ensures that critical functionalities are validated. ## My Second year in Tech URL: https://www.swapnoneel.site/blog/my-second-year-in-tech Date: 2024-11-08T21:33:04.263Z Summary: Exactly one year back from now, I published a blog where I shared my First year of Tech journey. In that blog, I’ve mentioned about how I got started, and balanced both development and DSA, while maintaining a consistent 9+ CGPA throughout the semest... Exactly one year back from now, I published a blog where I shared my First year of Tech journey. In that blog, I’ve mentioned about how I got started, and balanced both development and DSA, while maintaining a consistent 9+ CGPA throughout the semesters. Also, I shared how I got my breakthrough in freelancing and how I attended multiple hackathons and won all of them! If you haven’t read that blog, I would request you to [read it from here](https://swapnoneel.hashnode.dev/my-first-year-in-tech). Now, let’s talk about how much I progressed in the past one year, and see if I was able to meet my own expectations or plans that I had made for this year 👀, previously mentioned at the end of my last tech journey blog! ## The Journey ### November, 2023 As you have seen in my last tech-journey blog, the month of October was super productive for me, and that streak continued in November too. I managed to cross the 200+ problems mark on Leetcode and started doing Leetcode consistently. My goal was to solve at least one problem a day, and I made a routine for that,- whenever I’m opening my PC for the first time in a day, my target was to solve a single Leetcode problem at first and then move onto some other tasks! By the end of this month, I started participating in the Weekly Leetcode contests. This was among one of my to-do things from the first year of my journey! Alongside this, I was working on building a CRUD application called “Toile”, whose major purpose was to showcase my designing skill, alongside a functional backend. ![Toile CRUD web application design and dashboard](https://cdn.hashnode.com/res/hashnode/image/upload/v1731092158245/7fe5e731-cb6b-460d-ba3c-02720382b475.png) ### December, 2023 Completed the work on **Toile**! It took around a month to make it fully ready. But it turned out to be pretty good. You can check it out from here: [https://toile.vercel.app](https://toile.vercel.app) I started learning Dynamic Programming, because I noticed that there were a lot of questions coming from that during the contests. Also, I continued that habit of solving DSA problems regularly and managed to cross the 300+ mark of solving Leetcode problems. And, as I’ve solved all the Daily Problems of this month, I earned a badge for that. And most importantly, I got the opportunity at [Keploy](https://keploy.io) to work as a Community Evangelist. As a part of that role, I took an online session on “API Testing”, which went really really well, and also this was one of first talks to such a large audience! Additionally, I contributed to Keploy’s website and docs and helped them squash some bugs! Also, I ended this month with an amazing event DevFest Kolkata ‘24, where I learned about some new topics like Kotlin Multi-Platform, and was able to have a chat with multiple startup founders like Sakeet Thadad, Aloke Majumder from Hoichoi, Lakshya Mittal and got to experience an QnA session from Striver himself! Additionally, I designed a GitHub stats tracker, and as a part of that, I created a GitHub Wrap for myself! ![GitHub Wrap 2023 stats tracker interface](https://cdn.hashnode.com/res/hashnode/image/upload/v1731092247608/b63799de-6846-4b8b-8d91-3017ec5b280d.png) ### January, 2024 The year started with a bang! In the past few months, my freelancing was running a bit dry. But with the start of this year, I got two big projects from two different clients. One of them was a research-based technical writing and the other one was making the Career, Showcase and some other web pages for a small start-up! And, I maintained my consistency of solving DSA problems regularly and I was able to get the DPP Badge for this month too! ### February, 2024 The month started with a small contractual work for re-designing the UI of a start-up named Wizdom. So, there were a lot of revisions and talking extensively with the teams. As I was alone in the project, it was pretty exhilarating but I was able to deliver the task within two weeks. Although I’ve done UI/UX designing gigs before, but this was a bit different because it was a fully-working start-up rather than stand-alone clients! Also, me and my friend [Sam Maji](https://github.com/sammaji) decided to created a small SaaS project called, **InPoster**([https://inposter.vercel.app](https://inposter.vercel.app)) which will generate you viral LinkedIn posts with hashtags and other decorations just by taking some keywords from you about the topic. We challenged ourselves to build the project within one night, and we successfully did that, but it took some more days to improve some of the features and fix the bugs! ![InPoster viral LinkedIn post generator SaaS app](https://cdn.hashnode.com/res/hashnode/image/upload/v1731092318237/24a382a5-96bb-4d11-b7aa-108afad301e2.png) ### March, 2024 I updated my old portfolio site and revamped it completely. I focused a lot on the design this time and added a lot of animations using GSAP. You can check it out from here: [https://swapnoneel.vercel.app](https://swapnoneel.vercel.app) Also, this month, I completed solving 500 problems on Leetcode and got really consistent in participating in the contests, and achieved my highest rating of 1662 points! And one notable thing, I got an offer as a Front-End Engineer from a renowned start-up, but had to reject it because they wanted me as a full-time on-site employee, which was not possible for me because of my college and current scenario! ### April, 2024 This was the month of my end-semester exams, so definitely I was invested in that. But, I didn’t stopped practicing and solving DSA problems, and completed learning Graphs during this period of time. Also, I contributed a lot in [Keploy](https://github.com/keploy)’s Open-Source repositories this month, and created a sample app for them using Flask and MongoDB. Not only that, due to my excessive contribution to their projects, I was able to become one of the Top Contributors for the month! ![Top Keploy contributors of April 2024 leaderboard](https://pbs.twimg.com/media/GMZozT9b0AE6tAh?format=jpg&name=4096x4096) ### May, 2024 Maybe this was my most favorite month of the year! Started the month with a large freelancing work. It was related to image classification based on the locations from Google Maps (It may sound gibberish, but sadly I can’t elaborate more than this because of strict MoU). I had to get my hands dirty with image optimization, had to find innovative ways to store the images and make it available for my ML model at a steady stream. As I was on post-exam vacation, I was able to invest a lot of time in this and was able to deliver this project way before the deadline! Also, I worked on improving a old project of mine called [MAKAUT Buddy](https://makaut-buddy.vercel.app) which I had mentioned in my last blog. I revamped the Dashboard section completely, and made it more functional and feature-rich. Additionally, I wanted to create something unique and decided to create some `npm` packages. So, I created my first package called `swapnoneel`, which will give a short bio about me and all of my social handles when you run `npx swapnoneel` from your terminal. This was just the beginning, because I planned to create way more complex projects than this! And the best part about the month was that I got a remote opportunity to work as an **Intern with Keploy**, because of my extensive contributions to their projects in the recent times 🥳 ### June, 2024 Started this month while making a `npm` package called [`get-response`](https://www.npmjs.com/package/get-response). It’s a terminal based application that interacts with the Google's Gemini API to generate content based on the user input. It allows you to ask questions directly or provide context from files, images or directories, and get the response in a simple and easy to understand interface. Also, you can automate some terminal commands by prompting for the task. And additionally, we also have support for responses from Stack Exchange sites like Stack Overflow. It got over thousands of downloads and got extremely positive response. I incrementally updated it throughout the month and kept on adding more and more features to it. Delivered a python-based ML project for a research scholar, and as a part of it I had to explore Streamlit a bit, and while doing so, I made this app using Gemini’s API, that can convert your provided design into working HTML and CSS code. You can try it for yourself from here: [https://ui-to-code.streamlit.app](https://ui-to-code.streamlit.app) ![UI to Code Streamlit app preview](https://pbs.twimg.com/media/GQEH-MfaIAAJRVG?format=jpg&name=large) ### July, 2023 In my previous blog, I mentioned that I attended Eastern India’s biggest hackathon “Hack 4 Bengal 2.0”, but sadly we had to return mid-way because of an unavoidable circumstance. So, it was the time to make it a success in their next iteration, “[Hack 4 Bengal 3.0](https://www.hack4bengal.tech)”. So at very end of last month, me along with my other two teammates [Tuhin Poddar](https://github.com/Tuhin114) and [Sam Maji](https://github.com/sammaji) attended the hackathon. We created a cloud-based AI powered terminal called “[Term AI](https://term-ai.vercel.app)”, that let’s you code from anywhere at anytime from whichever device you want, which frees you from the hassle of carrying a laptop or relying on a desktop while travelling. It was really fun to make this project, because at this point we are making something unique that can be used by folks in their day-to-day life! ![Term AI cloud-based AI terminal interface](https://cdn.hashnode.com/res/hashnode/image/upload/v1731093334429/8cc5cee5-a976-4c33-a5dc-563e0ae4d62e.png) And, we ended up **winning the prize of second runner-up** among all of the 300+ participants. It was a really enjoyable moment for us, because from failing to stay throughout the hackathon in last year to winning it in this year, felt like a redemption arc for me! And it genuinely felt extremely good! ![Hack 4 Bengal 3.0 second runner-up award achievement](https://cdn.hashnode.com/res/hashnode/image/upload/v1731167476958/0c89adf8-a6cf-4c66-844a-e23159b3efa2.png) Also, I organized an open-source bootcamp under the hood of Keploy, called the “**Keploy Rain of Code**”, which led to an massive upsurge of open-source contributors for Keploy, and it was an unique experience for me to guide and manage so many folks all at once, and giving each one of them equal attention! The program was a great success and we were able to get a lot of new quality contributors. ### August, 2024 This was again the month of our end-semester exam, and I had to invest the majority amount of time on my college academics. So, it was kind of stale from my tech side. I just delivered a small technical writing project during this month. But, I kept in touch with DSA and continued solving more and more problems just like I was doing consistently throughout this period. ### September, 2024 This month was a bit hectic for me, as I was dealing with some personal problems, and also got two large development projects at the same time. So, I had to work for long hours straight. There were some days in these month where I didn’t even got the time to sleep. But the good thing is, I was able to deliver those projects successfully (and although, I can’t talk about the nature of the project or its tech-stack, but I can say it was unique and was an extremely enriching experience for me). ### October, 2024 This month, I created a RAG-based AI chatbot for Keploy, which is being trained on the Keploy docs and gives response for each and every Keploy-related question that you ask it! Other than this, I worked on delivering a couple of articles for a client and worked on creating the code documentation for an early-stage startup. Also, I worked on making a website design as a part of another gig. ## Bonus I have tried to mention most of the important stuff here. But I want to mention/clarify some things that weren't mentioned in the timeline or haven’t got reflected enough: - Solving DSA problems was a integral part of my journey, and almost everyday I practiced problems on Leetcode, starting from November, last year. And I’ve been highly consistent in doing that! ![LeetCode problem solving streak and statistics chart](https://cdn.hashnode.com/res/hashnode/image/upload/v1731097936631/9892cf9a-b21c-4b9d-9787-749937c80916.png) - I’ve mentioned about only the important or substantial freelancing works that have influenced my journey and have taken quite a few days or weeks to finish. Other than the mentioned ones, I’ve done a lot of other works in the freelancing space, which aren’t that worthwhile to mention. - In few places, I’ve mentioned about what I’ve done while working within Keploy. But please take note that these were just a few things that I really loved working on, or was worthy of mentioning here. Being a DevRel is a lot more than this! - I invested majority of my freelancing money in stocks or spent them in buying assets. And also, I gifted myself a Nothing Phone (2a) this year, and also built a completely new work-setup for me in our house! Other than these, I paid the semester fees this year, all by myself; and also have taken care of my closed ones. - In my last year’s blog, I had mentioned that I was mentoring my juniors and guiding them, which I did consistently throughout this year too, but I didn’t felt the need to mention it here and elaborating on that front. If you are eager to learn more about it, you can go through our [LinkedIn page](https://www.linkedin.com/company/the-async-devs/). - I was active on Twitter(X) throughout this time, and was able to grow my follower count up to 1800 people. You can check out [my X / Twitter profile](https://x.com/swapnoneel123)! And tried to be as active on LinkedIn as possible, you can check out [my LinkedIn profile](https://www.linkedin.com/in/swapnoneel-saha-14a3161b6/). ## Was I able to meet my expectations? Previously, in my first year of tech journey, I mentioned about some of the plans or goals that I want to achieve in my second year. Among them, I targeted to solve over 600+ Leetcode problems by the end of second year, and I was able to go way beyond that, and have solved just about 750 problems! I wanted to start delving deep into the domain of AI/ML, but sadly enough I was able to scratch only the surface. But I achieved my plan of making some SaaS products with my knowledge of AI/ML and Web Development, through InPoster, TermAI and Get Response. I planned to attend more hackathons, but I attended only one of them. This was an well thought out decision because rather than focusing on quantity, I had decided to focus on the quality and attended the hackathons that felt worthy of my time, efforts and skills. Although, I was prepared to attend two more hackathons, but due to exams and other schedule issues, I wasn’t able to participate in them. Also, I planned to delve into the topics of System Design and Operating System, which I did in this year. And, my plan of grabbing an internship and contributing to large-scale open-source projects also got fulfilled through Keploy. And finally, my plan of guiding my juniors was fruitful and I have been guiding them throughout this whole time, and have expanded our scope in doing so! ## My Plans for the Third Year I want to focus on learning AI and ML in-depth this year, which I wasn’t able to fulfill completely in my last year. And this is my highest priority right now. Also, I want to create some innovative projects on the way while learning it. I want to focus on my college academics a bit more, which I’ve completely neglected throughout my second year. And focus on the core subjects which will come really handy in future. I’m also looking forward for a full-time developer role or to get an SDE/SWE internship from a renowned organization. Also, I would like to participate in more and more coding contests, because although I had consistently started attending them this year but failed to do so because of timing issues. So, I would consider switching the platform and attend more and more contests as possible. Other than these, I want to face more challenges and learn new stuffs that will come across my way. And I plan to continue doing what I’ve been doing throughout these months, and maintain the same level of consistency! ## Conclusion And that's a wrap!! But if you have any queries regarding my journey or any suggestions for my future, I would love to hear that from you in the comments. And if you want to stay connected with me, you can follow me here or on my other social handles. Thank You for reading about my journey, have a nice day ahead!! ![Thank you graphic for second year in tech blog](https://cdn.hashnode.com/res/hashnode/image/upload/v1716652301849/8327a90c-373e-4837-9102-e67bb38def0c.png?auto=compress,format&format=webp) ## Functional Testing: An in-depth overview URL: https://www.swapnoneel.site/blog/functional-testing-an-in-depth-overview Date: 2024-11-05T00:05:40.000Z Summary: Functional testing checks whether an application does what its requirements say. Learn a practical workflow, common types, tool choices, and the habits that keep tests useful. Functional testing asks whether a feature gives the right result when you use it. You provide an input, perform an action, and compare what the application does with what the requirements say it should do. For a login form, that means checking a valid login, a wrong password, a missing field, and an account that should not be allowed in. It does not primarily ask how many requests the server can handle or how quickly the page loads. Those are non-functional concerns. ## What functional testing checks Functional testing verifies behavior from the outside. It can inspect inputs and outputs without knowing how the code is written, which is why it is often a [black box testing](https://keploy.io/blog/community/black-box-testing-and-white-box-testing-a-complete-guide) technique. ![Functional testing overview diagram](https://wp.keploy.io/wp-content/uploads/2024/11/ChatGPT-Image-Feb-17-2026-06_40_00-PM-1024x683.webp) Imagine the feature as a small machine. You put something in, the machine performs its rules, and you check what comes out. You do not need to see the gears to notice that a valid password opens the account while an invalid password does not. That does not mean the internal code is irrelevant. A tester may use implementation details to choose better cases, but the final check should describe behavior a user or another service can observe. ## Why it matters A feature can look correct in a code review and still fail at its boundaries. A checkout may calculate the normal total correctly but mishandle an empty cart. An API may return the right record for one user but expose another user's record when an ID changes. Functional tests give you a repeatable way to catch those mistakes. They also make a requirement concrete: instead of saying "the form should work," you record what happens for a valid value, an invalid value, and a missing value. One caveat: a large functional suite is not automatically a good suite. If every test follows the happy path, the green build can still hide the bug you are most likely to ship. ## A functional testing workflow ### Read the requirement first Start with the user story, acceptance criteria, API contract, or other description of expected behavior. Write down the inputs, the result, and any state change. If the requirement is vague, ask for an example before writing the test. For a login feature, your notes might say: a valid account receives access; an invalid password receives an error; a missing password does not send a login request. Those statements are easier to test than a general sentence about authentication. ### Write the test cases Each test case should identify the feature, any precondition, the action, and the expected result. Give the case a name that tells you what failed, such as `rejects_login_when_password_is_missing`. Include boundary cases and failure paths. For a file upload, test the allowed type, an oversized file, a forbidden type, and an interrupted upload. You do not need to invent every possible input, but you should test the decisions the feature makes. ### Prepare a representative environment Run the test in an environment with the same important settings as the application you plan to ship. That includes the database shape, feature flags, authentication setup, external-service stubs, and test data. The closer the environment is to reality, the more useful a passing result becomes. At the same time, keep test data controlled so that one test does not silently change what another test expects. ### Execute and compare Run each case manually or with an automated tool. Compare the actual status, response, screen, stored data, and side effects with the expected result. A page that displays the right message but still creates a record is not behaving correctly. ### Record defects clearly When a case fails, save the steps, input, expected result, actual result, environment, and any useful logs. A short title such as "wrong account returned after changing user ID" is easier to act on than "profile bug." ### Retest and run regression checks After the defect is fixed, rerun the failed case. Then run the related suite because a change to one feature can affect another. Keep a regression test for important bugs so the same behavior does not disappear during a later refactor. ## Types of functional testing The names below describe where the test sits or what it covers. They are not completely separate activities. 1. [Unit testing](https://keploy.io/blog/community/what-is-unit-testing/ "Unit Testing") checks a small function or component in isolation. Developers often run it close to the code they are changing. 2. [Integration testing](http://https://keploy.io/blog/community/integration-testing-a-comprehensive-guide "Integration Testing") checks whether two or more parts work together, such as an API and its database. 3. [System testing](http://https://keploy.io/blog/community/all-about-system-integration-testing-in-software-testing "System Testing") checks the complete application against its functional requirements. 4. [User acceptance testing](http://https://keploy.io/blog/community/what-is-user-acceptance-testing "User Acceptance Testing (UAT)") lets a client or representative user check whether the product supports the intended work before release. 5. [Smoke testing](http://https://keploy.io/blog/community/developers-guide-to-smoke-testing-ensuring-basic-functionality "Smoke Testing") is a short set of checks for the main paths, such as opening the application, signing in, and creating a basic record. It tells you whether deeper testing is worth starting. You can apply functional checks at each level. A unit test for a tax function and a system test for checkout are both functional tests, but they answer different questions. ## Manual and automated testing Manual testing is useful when you are exploring a new feature or judging something that is difficult to express as an assertion. A person can notice confusing wording, an awkward flow, or a visual problem that a script will not understand by itself. Automation is better for repeatable checks. It can run the same login, purchase, or API request after every change and report exactly where the result changed. Tools such as Selenium, [Keploy](https://keploy.io/ "Keploy"), Appium, and Cucumber support different parts of this work. Automation does require maintenance. Selectors change, test data expires, and product rules move. Keep the tests close to the behavior they protect, remove cases that no longer describe a real requirement, and fix failures instead of marking them as ignored forever. ## Tools and where they fit Selenium automates browser interactions, so it is useful for web application flows that a user completes through a browser. ![Selenium web automation platform logo](https://wp.keploy.io/wp-content/uploads/2024/11/selenium-2-1024x304.webp) Keploy can generate functional and regression cases from real application interactions. It captures requests and responses and turns those interactions into tests, which can help when manually describing every API case would take too long. ![Keploy test automation platform overview](https://wp.keploy.io/wp-content/uploads/2024/11/keploy_coverimg-1024x615.webp) Appium is used for mobile applications on Android and iOS. It supports native, hybrid, and [mobile web applications](https://keploy.io/blog/community/essential-functional-testing-tools-for-mobile-development "mobile web applications"), so the same testing idea can cover more than a desktop browser. ![Appium mobile testing framework logo](https://wp.keploy.io/wp-content/uploads/2024/11/appium_coverimg-1024x394.webp) Pick the tool from the boundary you need to test. A browser driver is not the best answer for a service contract, and an API test cannot tell you whether a button is confusing to use. ## Problems that make functional tests unreliable Complex applications create many possible paths. You cannot test every combination, so use the requirements and failure history to choose cases that protect important decisions. Frequent product changes create another problem. If the expected behavior changes, update the test with the requirement. If only a CSS class changes, avoid tying a browser test to that class when a stable label or role is available. Data is a common source of false failures. Missing records, shared accounts, expired tokens, and leftover state can make the same test pass in one run and fail in another. Give each test the data it needs, and clean up state when the test finishes. ## How to know the suite is useful Read a test name and ask whether you can predict the behavior it protects. Read the assertions and ask what bug would make them fail. If the answer is "almost none," the test is noise even if it runs quickly. Also look at the failures that reach users. A suite should grow around real defects, unclear requirements, and high-risk workflows. More cases are not always better; better cases are better. ## Keep the next check close Functional testing is the practice of turning expected behavior into repeatable checks. Start with the requirement, include the unhappy paths, isolate the test data, compare results and side effects, then retain the valuable cases in regression runs. When you automate a check, keep one human question in mind: what would a user notice if this stopped working? That question usually leads to a clearer test than copying the implementation line by line. ## Functional Testing FAQ ### How does functional testing differ from non-functional testing? Functional testing checks if the features work as expected, focusing on _what_ the system does. Non-functional testing, on the other hand, evaluates aspects like performance, usability, and reliability, focusing on _how well_ the system performs. Functional testing would ensure a login works, while non-functional testing might measure how fast the login loads or if it maintains security standards. ### What is black-box testing, and how does it relate to functional testing? Black-box testing is a technique where the tester examines the functionality of the software without needing to understand the internal code or architecture. Functional testing is often conducted as black-box testing since it focuses on inputs and expected outputs rather than the underlying code. ### Why do we need both manual and automated functional testing? Manual testing is essential for scenarios where human judgment is necessary, like assessing the usability of a user interface. Automated testing, however, is faster and ideal for repetitive tasks or larger applications. Together, they ensure thorough and efficient testing coverage. ### How does Keploy enhance functional testing? Keploy is an open-source platform that simplifies automated testing by enabling teams to create test cases from real application interactions and logs. This approach allows developers to generate meaningful tests based on actual user behavior, making it easier to catch edge cases and validate core functionalities. Keploy also supports automated test case generation, reducing the manual work involved in traditional functional testing while ensuring tests remain relevant and effective over time. ## How to Generate Random Numbers in JavaScript URL: https://www.swapnoneel.site/blog/javascript-random-number Date: 2024-11-01T01:20:01.000Z Summary: Use Math.random() for everyday variation, then build ranges, integers, booleans, choices, and shuffles around it. Use the Web Crypto API when randomness protects an identifier or secret. When you need a surprise in a game, a quiz, or a sample from a list, JavaScript gives you a starting point: `Math.random()`. It returns a decimal from 0 (included) up to 1 (not included). The number is pseudo-random, so it works for everyday variation but is not a security feature. The useful part is the small bit of math you put around that decimal. Once you understand that, random integers, choices, booleans, and shuffles all follow the same pattern. A related [JavaScript array filter guide on Keploy](https://keploy.io/blog/community/javascript-array-filter-method-guide) is available if you want to keep working with collections afterward. ## Start with Math.random() Call the function and store its result when you need to reuse it. The half-open range, written as `[0, 1)`, means 0 is possible while 1 is not. ```javascript const randomNum = Math.random(); console.log(randomNum); // A decimal greater than or equal to 0 and less than 1 ``` You should see a different-looking decimal on most runs: ![Console output of Math.random() generating floating-point number](https://wp.keploy.io/wp-content/uploads/2024/11/a8b9e0ce-0d09-4adb-ad38-f0e667c9-1.webp) That decimal is rarely the final shape you want. To create a value in another range, multiply it by the size of the range and then shift it by the minimum. ## Generate a number in a range This function returns a decimal greater than or equal to `min` and less than `max`: ```javascript function getRandomInRange(min, max) { if (min >= max) { throw new Error("min must be less than max"); } return Math.random() * (max - min) + min; } console.log(getRandomInRange(10, 20)); // A decimal from 10 up to, but not including, 20 ``` The expression `(max - min)` gives you the range width. Multiplying by `Math.random()` scales the result, and adding `min` moves it to the correct starting point. Here is the kind of output you might see: ![Console output of getRandomInRange function](https://wp.keploy.io/wp-content/uploads/2024/11/31462bb9-e5ae-48b7-bb57-3eb5495e-1.webp) ## Generate an integer in an inclusive range For a whole number, place `Math.floor()` around the scaled value. The `+ 1` matters when you want both endpoints included. Without it, the maximum value can never appear. ```javascript function getRandomIntInRange(min, max) { if (!Number.isInteger(min) || !Number.isInteger(max) || min > max) { throw new Error( "min and max must be integers, with min less than or equal to max" ); } return Math.floor(Math.random() * (max - min + 1)) + min; } console.log(getRandomIntInRange(1, 100)); // An integer from 1 through 100 ``` `Math.floor()` always rounds down. Since the scaled value is less than `max - min + 1`, the largest possible result after adding `min` is still `max`. The output will look similar to this: ![Console output of getRandomIntInRange generating random integer](https://wp.keploy.io/wp-content/uploads/2024/11/run-code-output.webp) ## Generate a random boolean A boolean needs only two outcomes. Comparing the random decimal with `0.5` gives you `true` about half the time and `false` the rest of the time. ```javascript function getRandomBoolean() { return Math.random() >= 0.5; } console.log(getRandomBoolean()); // true or false ``` You might see an output like this: ![Console output of getRandomBoolean function](https://wp.keploy.io/wp-content/uploads/2024/11/ed23a257-5fc0-4ed9-b5dd-a80ce6cd-1.webp) ## Pick a random array element Array indexes start at 0, so the largest valid index is `array.length - 1`. Multiplying by the length and flooring the result gives you an index that stays inside the array. ```javascript const colors = ["red", "green", "blue", "yellow"]; const randomColor = colors[Math.floor(Math.random() * colors.length)]; console.log(randomColor); // A color from the array ``` Here is an example of the resulting output: ![Console output selecting random element from array](https://wp.keploy.io/wp-content/uploads/2024/11/random-element.webp) ## Shuffle an array Sorting with a random comparator looks tempting, but it does not give every arrangement a fair chance. The Fisher-Yates algorithm is easier to reason about: start at the end, choose a random position from the part you have not shuffled, and swap the two values. ```javascript function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } const numbers = [1, 2, 3, 4, 5]; console.log(shuffleArray(numbers)); // Outputs a shuffled array ``` This function changes the original array and returns it. If you need to keep the original order, pass a copy with `shuffleArray([...numbers])`. The console output could look like this: ![Console output of Fisher-Yates array shuffle algorithm](https://wp.keploy.io/wp-content/uploads/2024/11/shuffled-array-output.webp) ## Do not build secure identifiers with Math.random() The old hand-written UUID example is worth treating as a warning. It uses `Math.random()`, which is not designed for secrets and does not provide the guarantees you want from a UUID. In modern browsers, use the built-in UUID method instead: ```javascript const id = crypto.randomUUID(); console.log(id); ``` The existing screenshot shows the output from the older generator: ```javascript function generateUUID() { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); } console.log(generateUUID()); // Outputs a random UUID ``` It can print a UUID-shaped string, but the shape alone does not make it safe for authentication tokens, password reset links, or other sensitive values. ![Console output of generateUUID function](https://wp.keploy.io/wp-content/uploads/2024/11/run-code-output2.webp) ## Use the Web Crypto API for security For passwords, tokens, keys, and other security-sensitive values, use the Web Cryptography API. `crypto.getRandomValues()` fills a typed array with secure random values: ```javascript function getSecureRandom() { const array = new Uint32Array(1); crypto.getRandomValues(array); return array[0]; } console.log(getSecureRandom()); ``` In browser code, `crypto` is available as the global Web Crypto object. In a Node.js project, use the crypto API provided by your Node version instead of assuming that `window` exists. ## The rule to remember Use `Math.random()` for ordinary variation, such as choosing a color or deciding which quiz question appears next. Use the Web Crypto API when someone could gain access, guess a value, or reset an account if the number is predictable. My caveat: randomness is easy to add and easy to misuse. Before reaching for a generator, decide whether you need a playful variation or a value that must resist guessing. For more JavaScript practice, see these related posts: [https://keploy.io/blog/technology/mastering-nyc-enhance-javascript-typescript-test-coverage](https://keploy.io/blog/technology/mastering-nyc-enhance-javascript-typescript-test-coverage) [https://keploy.io/blog/community/javascript-var-vs-let-vs-const](https://keploy.io/blog/community/javascript-var-vs-let-vs-const) ## Top 5 Best IDEs to use for Python in 2024 URL: https://www.swapnoneel.site/blog/best-ides-for-python-in-2024 Date: Fri, 25 Oct 2024 12:13:35 GMT Summary: PyCharm, VS Code, Spyder, Jupyter, and Thonny each fit a different Python workflow. Compare their strengths, limits, and the kind of work each makes easier. The best Python editor is the one that makes your next task easier. That may be a full IDE with refactoring and debugging, or it may be a small editor with a terminal beside it. So, which one should you install? It depends on what you are doing with Python. A data scientist opening a notebook has a different problem from a beginner tracing their first loop, and both have a different problem from someone maintaining a large web application. These five options fit those kinds of work in different ways, so pretending one tool wins every category would be misleading. My pick is at the end, but keep your machine, project size, and patience for configuration in the decision. ## PyCharm PyCharm is the easiest recommendation for a large Python project. It understands a project as more than a folder of files, so navigation, refactoring, debugging, virtual environments, and Git all live in one place. ![PyCharm Professional IDE interface](https://textdata.cn/images/blog/pycharm-professional.png) The editor can complete code, flag errors while you type, and rename symbols across a project. Its debugger is comfortable once you learn the controls, and the Professional edition adds support for web frameworks such as Django and Flask. The tradeoff is weight. PyCharm can use a lot of memory, especially on an older machine, and the Professional edition is paid. The free Community edition is enough for many Python projects, but check which features you need before building your workflow around one edition. Choose PyCharm when you want Python-specific tools ready when you open the project. I would not choose it for a quick one-file script unless I already had it open. ## VS Code VS Code is the most flexible option in this list. It is a general editor rather than a Python IDE out of the box, but the Python extension adds code completion, debugging, environment selection, and test support. ![VS Code Python development environment](https://external-preview.redd.it/Uz0PH-r8nGx8gU9UCHURirqrtXgLhtqJiNDVgT03jtw.jpg?auto=webp&s=580d0dc62b6581d2a8ff8e628d3bea5d53bdf5d2) The built-in terminal and Git view make it easy to move between editing, running a command, and checking a change. You can also add support for Docker, notebooks, JavaScript, and many other tools without leaving the editor. That flexibility has a price. New users can spend more time choosing extensions and settings than writing Python. When the wrong extension takes over formatting or the interpreter points at the wrong environment, the editor does not always make the cause obvious. Choose VS Code if you work across several languages or want one editor that can grow with your projects. It is my general recommendation, even though it takes more setup than PyCharm. ## Spyder Spyder is built around data exploration. Its layout gives you an editor, an interactive console, plots, and a variable explorer in the same workspace. You can run a few lines, inspect the resulting DataFrame, and keep going without adding print statements everywhere. ![Spyder scientific Python IDE](https://www.spyder-ide.org/assets/media/website_screenshot.png) That variable explorer is the reason to try Spyder. Seeing arrays and tables directly is useful when you are learning how a calculation changes the data. Spyder is free and open source, and it is often installed alongside Anaconda. It is less comfortable for web applications or a general software project with many packages and services. The project tools are not as broad as those in PyCharm or VS Code, so the editor can feel like the wrong shape once your work stops being notebook-like. Choose Spyder when your day is mostly NumPy, Pandas, plots, and experiments. ## Jupyter Notebook Jupyter Notebook is an interactive environment rather than a traditional IDE. You write code in cells, run one cell at a time, and place Markdown explanations beside the output. ![JupyterLab interface preview](https://jupyter.org/assets/homepage/labpreview.webp) That workflow is excellent for exploratory data analysis, machine learning experiments, and teaching. You can show the code that produced a chart directly next to the chart, which makes a notebook easy to share as a record of an investigation. The same flexibility can make a notebook messy. Cells can run out of order, hidden state can survive longer than you expect, and a project spread across several notebooks is harder to maintain than a normal Python package. A notebook is a poor place to hide application logic that needs regular tests. Choose Jupyter when you want to ask questions of data and see the answer immediately. Move reusable code into `.py` files once the experiment starts becoming a product. ## Thonny Thonny is aimed at people who are learning Python. The interface removes many distractions, and its debugger lets you step through a program while watching values change. ![Thonny IDE for beginner Python developers](https://thonny.org/img/screenshot.png) That visual step-through is useful because beginners often know what a line says but not when it runs or what value it leaves behind. Thonny makes those changes visible without asking you to configure a large toolchain first. It is free and friendly, but it is not designed for a large application. The extension and customization choices are limited, and you will probably outgrow it once you need a more complex project layout or a broad set of integrations. Choose Thonny if you are learning the language and want the editor to stay out of your way. ## How to choose without overthinking it Start with the work you will do most often. If you need a debugger and project-wide refactoring, try PyCharm or VS Code. If you inspect tables and plots, try Spyder or Jupyter. If you are learning your first loops and functions, Thonny is enough. Also consider the cost of a tool you will not use. A large IDE cannot fix unclear requirements, and a notebook cannot give a production service a test suite. Your editor should support the next problem you expect to solve, not the most impressive screenshot. ## My pick For a general Python workflow, I would pick VS Code because it handles Python well and leaves room for other languages and tools. PyCharm is the better choice when Python is the whole project and you want its deeper project support without assembling extensions. That is my winner for this list, but that's just me, and your workflow might be different. There are plenty of other editors worth trying. Give each option one real task instead of judging it from a feature page. The small annoyances show up when you create an environment, run a test, jump to a definition, and debug a failing line. For more posts, you can follow me on [Twitter (swapnoneel123)](http://twitter.com/swapnoneel123). My [GitHub (Swpn0neel)](https://github.com/Swpn0neel) has some of my projects too. ## Understanding Semantic Versioning URL: https://www.swapnoneel.site/blog/understanding-semantic-versioning Date: Sat, 01 Jun 2024 19:35:38 GMT Summary: Learn the basics of Semantic Versioning and how it helps developers manage software changes effectively... Often enough, we see version numbers like 1.0.2, 2.5.6 or something similar, associated with a software product. But, you may think what do these numbers actually represent, and why do we use them? We could've simply used numbers like 1, 2, 3, etc. for naming each versions! But, let me tell you, we use them because we’re following a best practice called Semantic Versioning. When we use Semantic Versioning, developers can easily predict whether a change will break their code or not. And also, the numbers give a clue to the kind of changes that have occurred. Now, let's dive deep, and understand each and everything about semantic versioning! ## What is Semantic Versioning? Semantic Versioning, sometimes abbreviated to SemVer, is a software versioning scheme using a three-part number format MAJOR.MINOR.PATCH, and this versioning scheme helps developers and users understand the nature of changes in the software. ![Semantic versioning MAJOR.MINOR.PATCH structure](https://cdn.hashnode.com/res/hashnode/image/upload/v1717267689583/94e58a9e-c8c5-4fbc-9a80-29081327b784.png) For the 2.6.8 version, for example: - 2 is its MAJOR version. - 6 is the MINOR version. - 8 is the PATCH version. Now, let's break down these 3 components and try to understand them individually: ### Patch Version Patch versions are generally used for bugfixes, and there are no functionality changes. You have heard about the term "hotfix", it's the patch version update that fixes any breaking changes! ![Patch version increment diagram](https://cdn.hashnode.com/res/hashnode/image/upload/v1717267567041/f64b3f07-0d5d-4c47-90d6-a5af7e6f262a.png) When you increase a new patch, you increase the rightmost number by 1. From 1, you increase it to 2, then to 3, and so on and to keep in mind, there are no limits to these numbers, so once you reach 9, you continue from 10, 11, 12 and so on. ### Minor Version The second number in the middle is called the minor version number. It is used when you release new functionality in your project. It could be a completely new feature or some added functionality to an already existing feature. ![Minor version increment diagram](https://cdn.hashnode.com/res/hashnode/image/upload/v1717267591031/fd410534-b0a2-46f0-abd8-2318835f5031.png) When you increase the minor version, you also increase it by one, and just like patch versions, it doesn't have any limits. But when you increase the minor version, you must reset the patch version to zero. ### Major Version The leftmost number is a major version. When you increase the major version, you tell people that there are backward-incompatible changes. People may experience breakage if they use the next version. It's often associated with a complete overhaul of the product. But, in some cases, it may also happen that we increment the major version, when a lot of new features along with a lot of improvements to the pre-existing features are launched at once! ![Major version breaking change diagram](https://cdn.hashnode.com/res/hashnode/image/upload/v1717267684973/accc1756-5772-478f-83dc-dcc5d7c7c6fa.png) When you increase the major version number, you have to reset both patch version and minor versions. ## Additional version types The above mentioned three version types are well-defined by some systematic rules, but for the upcoming version types or details that we are about to discuss doesn't have any proper rules and regulations and it varies between different products and organizations. ### Pre-release version If you want to create a pre-release version (like an alpha or beta version), you can add a hyphen `-`, followed by the words `alpha` or `beta`. There are no hard and fast rules for pre-releases, so you can name them anything you want. Usually, we use alpha or beta, followed by a number. For example, version `2.0.3-alpha2` is the second alpha release built on top of version `2.0.3` of the actual product. A pre-release version means the version is unstable and might not meet the intended final quality level. It often contains known and identified bugs, which are desired to be fixed, and even incomplete feature implementation. It's often released for closed/public testing to gather feedback or just to test the current implementation. ### Build Metadata Build metadata are specified by appending a plus sign and a series of dot-separated identifiers. For example, version 1.2.0+20130313144700. It provides details beyond the core version number, typically used for internal tracking purposes during development and testing. You can encode full commit id in the build metadata, so it's faster to analyze from what commit this software was actually build on, or you can encode the date/time of the particular build, machine it was build on. ## Initial Development Version `0.y.z` is for software in the initial development stage. Software using a 0 MAJOR version indicates that it is at an early development phase, and with any version, anything might get changed. During this period, the product is considered to be unfit for public usage, and is purely in the development stage. During this phase, we generally start our project with version `0.1.0` and when we reach a stable state and our product is almost feature complete for the launch, we generally test it in the `alpha` and `beta` release. After that, we increase the version to `1.0.0` upon release. ## Conclusion By now, I think you have realized that by providing clear rules and guidelines for incrementing version numbers, it helps developers communicate changes effectively, manage dependencies, and ensure predictable updates. Also, if you want to see an example of how semantic versioning is implemented in a real-world project, you can check out my latest project [Get Response](https://www.npmjs.com/package/get-response), which is a Node.js based command-line interface (CLI) tool that interacts with the Google's Gemini API to generate content based on the user input, and can also automate task for you. Hence, that's a wrap for now, and if you want to learn new things like this, you can follow me at [Swapnoneel Saha](https://hashnode.com/@Swapn0neel) and also follow me on [Twitter (swapnoneel123)](http://twitter.com/swapnoneel123) where I share more such content through my tweets and threads. I wish you a great day ahead and till then keep learning and keep exploring!! ![Thank you graphic for semantic versioning blog](https://cdn.hashnode.com/res/hashnode/image/upload/v1716652301849/8327a90c-373e-4837-9102-e67bb38def0c.png) ## Publishing your own NPM Package is too simple!! URL: https://www.swapnoneel.site/blog/publish-your-own-npm-package Date: 2024-05-25T15:55:48.764Z Summary: Introduction Isn't it cool if you can run command like npx in someone's terminal, and they can read about you, your works, and find details like your social media handles, right at their terminal? Or maybe you have created some cool React ... ## Introduction Isn't it cool if you can run command like `npx ` in someone's terminal, and they can read about you, your works, and find details like your social media handles, right at their terminal? Or maybe you have created some cool React component, wouldn't that be even more cooler if other folks can use them in their projects just by installing an `npm` package? I think you have got the drill already... Publishing your own NPM package is really cool and can help a lot of folks globally!! So, before we start about how to publish your own NPM package, I think you should run these two commands first at your terminal `npx swapnoneel` and `npx get-response`, to properly understand what I mean by "cool" and "helpful"!! ## Create your NPM account This is the most obvious step, if you want to publish your own package!! Just head over to [https://www.npmjs.com](https://www.npmjs.com) and create your account. Also, remember to do two-factor authentication of your account. You can use an Authenticator app like [TOTP Authenticator](https://play.google.com/store/apps/details?id=com.authenticator.authservice2&hl=en&gl=US)!! We will talk later, about how and when to use our account. For now, let's continue to the next step!! ## Choose the name of your NPM package The very first thing you need to do before creating your package is to choose a name. This is important because your package needs to have a unique name and you cannot choose a name that has been used already. After you choose a name, you've to go to the [NPM registry](https://www.npmjs.com/) and run a search. Be sure there's no exact match to the name you chose or a match that is too similar. Otherwise, you have to try choosing a different name!! ## Let's start creating the NPM package As I've stated in the title itself, that creating your own NPM package is extremely simple; now you will understand, why! ### Install Node and NPM If you don't have Node installed already, you should go and refer to [this article](https://swapnoneel.hashnode.dev/nodejs-npm-nvm) and install it. In the article, you will find the step-by-step guide about how to install and maintain the different Node versions!! ### Initialize git repository Create a new project folder for your package and navigate into the folder. Then, run the following command in your terminal: ```bash git init ``` This will help us track the changes in our package. Also, make sure that it remains hosted on GitHub!! ### Initialize NPM in your project To do this, navigate to the root directory of your project and run the following command: ```bash npm init ``` Now, you will prompted to give some answers, I'm mentioning the utility of each one of them: - `package-name`: It should be the name of your package that you want to publish. Also, remember that it must be lowercase and may only include hyphens. - `version`: The initial value is `1.0.0`. You may change it to `0.1.0` and release the full version `1.0.0` later!! You will be updating the version number each time when you update your package using [semantic versioning](https://www.freecodecamp.org/news/semantic-versioning-1fd6f57749f7/). - `description`: You can provide a description of your package here, explaining about what your package does and how to use it. - `entry point`: The entry file for your code. The default value is `index.js`. - `test command`: Here, you can add the command you want to run when a user runs `npm run test`. To keep things simple, you may keep it blank for now!! - `git repository`: The link to your remote repository on GitHub. It's optional, but it's better if you can add the GitHub repository link here!! - `keywords`: Add relevant keywords that will help others find your package on the NPM registry. It's optional too, and you can leave it blank for now!! - `author`: Add your name. - `license`: You can add a license or use the default license, Internet Systems Consortium (ISC) License. Once you provide all these information, a file will be created called `package.json` that will kinda look like this: ![package.json file generated after running npm init](https://cdn.hashnode.com/res/hashnode/image/upload/v1716639984976/535670bf-65cd-4641-9b48-1be0daef046b.png)
💡
Additionally, you should modify the package.json file to add "type" : "module" which will enable you to write the JavaScript code in ECMAScript modules format. This allows for cleaner code organization, code reusability, and better encapsulation.
### Write your code Now, you can go ahead and write your code for the package. For this tutorial, I will be writing my code in the `index.js` file. **💡 Reminder!!** _Please add the following line at the start of the code because it tells the operating system to use the Node.js interpreter to execute the script. It allows us to run the script by simply typing its filename, without having to specify the full path to the Node.js interpreter._ ```javascript #!/usr/bin/env node ``` Now inside the `index.js` file, write the code for your package. Here, I will be creating a simple package called `test`. This package will print the string `"Reminder to follow Swapnoneel on Twitter at https://x.com/swapnoneel123"` in the terminal. ![index.js code implementation printing reminder text](https://cdn.hashnode.com/res/hashnode/image/upload/v1716648802856/1df1543f-5bb4-43e6-affa-3f4461378f83.png) ### Create an executable script Now, we have to make this `index.js` file executable and assign it to a command, so that whenever we run that command, we get the result output-ed by `index.js` . For doing that, we have to add this in out `package.json` file: ```json "bin": { "test": "./index.js" }, ``` So, after all the changes, our `package.json` file should look somewhat like this: ```json { "name": "test", "version": "1.0.0", "description": "just to test how to publish npm package", "main": "./index.js", "type": "module", "bin": { "test": "./index.js" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "git+https://github.com/Swpn0neel/test.git" }, "author": "Swapnoneel Saha", "license": "ISC", "bugs": { "url": "https://github.com/Swpn0neel/test/issues" }, "homepage": "https://github.com/Swpn0neel/test#readme" } ``` ### Make the script executable Now, to make this script executable, so that works perfectly, we have to run this command in our terminals: **Linux** ```bash chmod +x index.js ``` **Windows** _Note that, for this to work perfectly, you need to have_`git`_installed on your system!!_ ```bash git update-index --chmod=+x index.js ``` ## Test your NPM package Testing ensures that your NPM package works as expected. To do that, first navigate to the root of your project. Then, run the following command: ```bash npm link ``` This will make your package available globally. And you can require the package in a different project to test it out. For that, you go outside the root directory and try running the following command to add the package you have just created: ```bash npm link ``` And, you'll see something like this: ![Terminal output of npm link command testing package locally](https://cdn.hashnode.com/res/hashnode/image/upload/v1716650345968/b8038b49-3ee6-4ce2-8e78-f61b04b60a84.png) Now you can run your package, and it should work perfectly!! ![Terminal output of running local package command](https://cdn.hashnode.com/res/hashnode/image/upload/v1716650425160/b8d12272-5801-4c13-87fa-243a88244d8f.png) ## _Publish Your NPM Package_ To publish your package on the NPM registry, you need to have an account. And, I hope you have created that previously!! So, now it's time to open your terminal and run the following command in the root of your package: ```bash npm login ``` You will get a prompt to enter your `username` and `password`. If login is successful, you should see a message like this: `Logged in as on https://registry.npmjs.org/.` You can now run the following command to publish your package on the NPM registry: ```bash npm publish ``` Note that, during these steps you may need to use your authentication app to authenticate the procedure!! And so if you have been following along, then congratulations! You just published your first NPM package. And, you can visit the [NPM website](https://www.npmjs.com/) and run a search for your package. You should see your package show up in the search results. ## Conclusion And now, you can probably boast about your coolness, because yes, your tool can be accessed anywhere in the world in anybody's terminal!! If you want to know how vast tools you can make through this, head over to [Get Response](https://www.npmjs.com/package/get-response), which is a node.js based command-line interface (CLI) tool that uses the Google's Gemini to generate content based on the user input. This tool allows you to ask questions directly or provide context from files or directories, and get the response in a simple and easy to understand interface. And finally, thank you for reading the blog! I hope you found it informative and valuable. For more information, follow me on [**Twitter (swapnoneel123**](http://twitter.com/swapnoneel123)**)** where I share more such content through my tweets and threads. And, please consider sharing it with others on **Twitter** and tag me in your post so I can see it too. You can also check my [**GitHub (Swpn0neel)**](https://github.com/Swpn0neel) to see my projects. I wish you a great day ahead and till then keep learning and keep exploring!! ![Thank you graphic for npm publishing guide](https://cdn.hashnode.com/res/hashnode/image/upload/v1716652301849/8327a90c-373e-4837-9102-e67bb38def0c.png) ## Installing Node.js and managing it's versions were this easy? URL: https://www.swapnoneel.site/blog/nodejs-npm-nvm Date: 2024-05-24T14:42:50.142Z Summary: Introduction We all know that, Node.js is the runtime environment for JavaScript that executes JavaScript code outside the web browser. And, npm stands for Node Package Manager and is the default package manager for Node.js and is also a platform for... ## Introduction We all know that, **Node.js** is the runtime environment for JavaScript that executes JavaScript code outside the web browser. And, **npm** stands for Node Package Manager and is the default package manager for Node.js and is also a platform for managing JavaScript packages. It provides a command-line interface (CLI) for interacting with the npm registry, which hosts thousands of open-source libraries and modules. ## How to install node.js and npm locally? Based on the operating systems, the installation process is different and varied. That's why, I'm classifying them here so that you folks don't get confused between them. ![Node.js architecture and runtime overview](https://cdn.hashnode.com/res/hashnode/image/upload/v1716554172337/62142e92-545a-4b6b-9b5e-b018bcf9d0e8.png) ### Windows 1. **Download the Installer**: - Go to the official Node.js website: [nodejs.org](https://nodejs.org/). - Download the LTS (Long Term Support) version, because it happens to be the most stable version. 2. **Run the Installer**: - Open the downloaded `.msi` file and run the installer. - Follow the installation steps, and please **make sure to check the box that says "Install Node.js and npm."** ### _macOS_ 1. You have to install **Homebrew** if you haven't already. To do that, open the terminal and run: ```bash /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ``` 2. Now, we can install `node.js` and `npm` with Homebrew: ```bash brew install node ``` ### _Linux_ It's extremely easy to install `node.js` and `npm` in Linux. Just run these two commands in the terminal: ```bash sudo apt update sudo apt install nodejs npm ``` ## Verify your installation You can verify the installation of `node.js` and `npm` by running the following two commands in your terminal: ```bash node -v npm -v ``` > _If you are on Windows, Powershell may cause some issues, so try to use Command Prompt in that case. It will work!!_ ![Terminal verification of node and npm versions](https://cdn.hashnode.com/res/hashnode/image/upload/v1716553688790/eaf89f25-9858-410e-9ebc-082022a6697e.png) ## Managing node versions using NVM NVM stands for Node Version Manager and it's a command-line tool that helps us to manage and switch different versions of Node.js with ease and convenience!! ![NVM Node Version Manager overview banner](https://cdn.hashnode.com/res/hashnode/image/upload/v1716554276508/db2ae459-9cd3-41fe-837f-29c2ea3d8671.png) 1. ### Check available node versions Before installing any node version, let's first check the available Node versions. To do that, we can simply run the following command in our terminal: ```bash nvm ls available ``` After running this, you get something like this: ![Terminal output of nvm ls available command](https://cdn.hashnode.com/res/hashnode/image/upload/v1716555882030/1d1e966c-c139-4c92-a68e-a64cbde6b661.png) 2. ### Installing the latest node version To install the latest version of node.js, you can simply run this command, ```bash nvm install latest ``` But remember, it's always better to install the LTS (long-term support) version of node, because it's less buggy and is overall more stable!! To install the LTS version of Node, run the command,- ```bash nvm install lts ``` 3. ### Install multiple Node versions One of the most interesting part of NVM is you can install multiple versions of Node at the same time and use any of them based on your convenience!! For this, nvm has the `nvm install` command. You can install specific versions by running this command followed by the version you want. For example, ```bash nvm install 18.17.0 nvm install 20.11.1 nvm install 20.12 ``` Also, as NVM follows semantic versioning, you can install v18.17 and use any of the following version under 18.17, like 18.17.0, 18.17.1, etc. Here, 18 represents the major version, 17 represents the minor version, and 1 represents the patch version!! 4. ### Installing specific Node versions You can install any specific node version, by running this command,- ```bash nvm install ``` Replace the `` with your desired Node version. But, to ensure that your given version is valid, make sure to run `nvm ls available` and put a correct version from the list!! Also, once you install a version of Node, the corresponding version of NPM is also installed alongside with it. So you don’t need to install NPM separately!! 5. ### Check installed Node versions in your system To check the list of all node versions that you have installed on your system, you can simply run,- ```bash nvm list ``` And, you will see a response like this: ![Terminal output of nvm list showing installed Node versions](https://cdn.hashnode.com/res/hashnode/image/upload/v1716560504142/21e3f56e-e0d9-415f-96e9-816e2ccb40da.png) 6. ### Switching Node versions As you can see in the previous image that I'm currently using `20.13.1`. Now, if I want to switch my version to another one like `18.17.0`. I can simply use the following command: ```bash nvm use 18.17.0 ``` In your case, you can put your desired version in place of `18.17.0`, but first make sure it is a valid version number and it is installed on your system!! 7. ### Uninstall a Node version To uninstall an already installed Node version that you no longer think is useful, you can do that by running the command,- ```bash nvm uninstall ``` Replace the `` with your desired and installed Node version. ## Conclusion > TLDR: This article provides a comprehensive guide on installing Node.js and npm across different operating systems (Windows, macOS, and Linux), verifying installations, and managing Node.js versions using NVM (Node Version Manager). It covers steps to install specific Node.js versions, switch between versions, and uninstall versions when no longer needed. The guide emphasizes the importance of using the LTS (Long Term Support) version for stability and includes detailed commands for each process. Well, that's a wrap for now!! Hope you folks have enriched yourself today with lots of known or unknown concepts. I wish you a great day ahead and till then keep learning and keep exploring!! ![Thank you graphic for Node.js npm NVM guide](https://cdn.hashnode.com/res/hashnode/image/upload/v1716561409967/1916dd93-56c0-4df1-8be1-19b75342aad1.png) ## Software Testing Pyramid URL: https://www.swapnoneel.site/blog/software-testing-pyramid Date: Mon, 29 Apr 2024 12:25:01 GMT Summary: The testing pyramid places many fast unit tests below fewer integration tests and a small number of end-to-end journeys. Use it as a feedback and maintenance guide, not a rigid law. Software testing asks a plain question: does the program behave the way you expect when someone uses it? A useful test gives you evidence before a change reaches a user, and a failing test gives you a smaller place to start looking. The testing pyramid is a way to divide that evidence. You put many fast tests at the bottom, fewer tests that check real connections in the middle, and a small number of full user journeys at the top. The shape is a reminder about feedback speed and maintenance cost, not a law that every project must follow exactly. ## The three layers ![Software testing pyramid architecture diagram](https://cdn.hashnode.com/res/hashnode/image/upload/v1714334533795/f1fd8287-3ef7-40d5-9c49-1f9b585f2235.png) Read the pyramid from the bottom upward. Each layer answers a different question, and each one catches failures that the others can miss. ### Unit tests check one piece of logic A unit test exercises a small part of your code, usually a function, method, or class, without starting a database or calling a remote service. You give the unit an input, make a prediction, and check the result. These tests are usually quick to run, which makes them useful while you are editing code and in continuous integration. They also make failures easier to inspect because fewer moving parts are involved. The tradeoff is isolation. A unit test can prove that a function handles a discount correctly, but it cannot prove that your checkout service sends the right value to the payment provider. ### Integration tests check connections Integration tests exercise the boundary between pieces of a system. That might be an HTTP handler talking to a service, a repository talking to a database, or two modules passing data between each other. The test setup is heavier because you need more than the function under test. You might start a test database, provide a configured client, or run a local service. That extra work makes these tests slower, but it also lets them catch mismatched fields, bad serialization, missing configuration, and other connection problems. You do not need to avoid every fake dependency here. The useful question is which boundary you want to check. If the database is the subject, use a real isolated database. If an external billing service is unavailable in tests, a controlled substitute may be the safer choice. ### End-to-end tests check a user journey End-to-end tests drive the application as a user would. A test might open the login page, submit credentials, and check that the account page appears. It crosses the browser, application server, data store, and other configured pieces. That realism comes with a price. These tests take longer, need more setup, and can fail because of a browser timing issue or an environment problem. Keep them for flows where a failure would matter to a user, then use the lower layers for the many smaller cases underneath. ## Why the shape matters Suppose a tax calculation is wrong. A unit test can point to the calculation within seconds. If the only test is an end-to-end checkout test, you first wait for the whole flow, then inspect several services before finding the same mistake. The pyramid pushes most feedback toward the cheaper layers. It also limits the amount of setup you have to maintain. A project with only end-to-end tests can work, but each small code change may require a long, fragile journey before you know whether the basic logic still works. My caveat is that the picture can make teams chase a ratio instead of useful coverage. A small service with a few carefully chosen integration tests may not look like a perfect pyramid, and that is fine if those tests protect the real risks. ## How to build the layers Start with the behavior that would hurt if it broke. Write unit tests for pure calculations and branching logic. Add integration tests around the boundaries where data changes shape or leaves your process. Add end-to-end coverage for the shortest set of journeys that represent the product's most important actions. Run the fast tests on every change. Run integration tests in an isolated environment often enough to catch broken connections before release. Run end-to-end tests as part of the delivery checks, but do not make every small assertion depend on a full browser session. When a test fails, fix the test or the product code rather than making the assertion weaker just to get a green build. Also, delete tests that duplicate a lower layer without checking anything new. Test code is still code, and stale test code can waste your time. ## Where teams get stuck The first obstacle is usually setup. A new test suite needs a runner, fixtures, test data, and a place to run safely. Start with one narrow path and make it repeatable before adding more cases. Maintenance is the next cost. Tests that know too much about implementation details break during harmless refactors. Assert the behavior a user or calling function depends on, not every internal step used to produce it. The final trap is treating speed as the only measure. Fast tests that never exercise a real boundary will not find a broken database query. Slow tests that cover every tiny branch will make feedback painful. The layers work when each one has a job. ## The practical takeaway Use the pyramid as a conversation about risk. Unit tests give you quick answers about local logic, integration tests check that neighboring parts speak the same language, and end-to-end tests confirm that important journeys work from the outside. There is no prize for drawing the most symmetrical pyramid. Put the tests where they can tell you something useful, keep the expensive journeys focused, and make the fast checks easy to run before you forget why the change was made. ![Thank you graphic for software testing pyramid blog](https://cdn.hashnode.com/res/hashnode/image/upload/v1714334190050/d203af69-a3d1-4e93-827b-1ecb696255b9.png) ## The 3 Most Powerful Functions in JavaScript URL: https://www.swapnoneel.site/blog/the-3-most-powerful-functions-in-javascript Date: 2024-03-10T12:14:24.268Z Summary: map(), filter(), and reduce() cover three common array tasks: transforming values, keeping matches, and combining items into one result. An array is useful because it keeps related values together. The awkward part begins when you need to change every value, keep only some values, or combine all of them into one result. That is where `map()`, `filter()`, and `reduce()` fit. They are higher-order functions because they receive another function as an argument. You describe the operation once, and JavaScript calls it for the array values. You will see these methods in browser code, server code, and UI libraries such as React. The syntax is compact, but the idea underneath is simple: transform, select, or combine. ## The map() function Use `map()` when every item should produce a corresponding item in a new array. The original array stays unchanged, and the new array has the same number of positions unless your callback changes the value to something else. ### Syntax ```javascript array.map((element, index, array) => { // return the new value for this element }); ``` The callback receives the current `element`, its `index`, and the full `array`. Most of the time, the element is enough. ### Example Here, each number is multiplied by itself. The input remains `[1, 2, 3, 4, 5]`, while `squaredNums` receives the new values. ```javascript const nums = [1, 2, 3, 4, 5]; const squaredNums = nums.map((num) => num * num); console.log(squaredNums); // [1, 4, 9, 16, 25] ``` The browser console shows the transformed array: ![Browser console output of JavaScript map() function](https://cdn.hashnode.com/res/hashnode/image/upload/v1710071277175/f965ca2a-4cbb-4009-900e-22673b31f60f.png) If your callback does not return a value, the new array contains `undefined` for those positions. That small mistake is common when a block-bodied arrow function uses braces but forgets `return`. ## The filter() function Use `filter()` when you want a smaller array containing only the items that pass a test. The callback should return a truthy or falsy value. JavaScript keeps the item when the result is truthy and skips it when the result is falsy. ### Syntax ```javascript array.filter((element, index, array) => { // return true to keep the element }); ``` The callback receives the same three arguments as `map()`, but its job is different. You are answering "keep this item?" rather than creating a replacement value. ### Example This callback keeps positive numbers and removes the negative ones: ```javascript const nums = [1, -2, 3, 4, 5, -6, -7]; const positiveNums = nums.filter((num) => num > 0); console.log(positiveNums); // [1, 3, 4, 5] ``` Here is the corresponding browser output: ![Browser console output of JavaScript filter() function](https://cdn.hashnode.com/res/hashnode/image/upload/v1710071131323/f9ec94cf-bdde-4df3-a116-85267d8b26aa.png) An empty result is not an error. It simply means that no item passed the test. That makes `filter()` useful for searches, permission checks, and lists where the visible items depend on a condition. ## The reduce() function Use `reduce()` when an array should become one final value. That value can be a number, string, object, or even another array. Because `reduce()` can do many jobs, it is also the method most likely to become hard to read. ### Syntax ```javascript array.reduce((accumulator, currentValue, index, array) => { // return the accumulator for the next iteration }, initialValue); ``` The `accumulator` carries the result forward. `currentValue` is the item being processed now. The `initialValue` gives the accumulator a known starting point, which also keeps the behavior clear when the input array is empty. ### Example To multiply all the numbers, start the accumulator at `1`. The first pass multiplies `1` by `1`, the next pass multiplies that result by `2`, and so on until the final product is returned. ```javascript const nums = [1, 2, 3, 4, 5]; const product = nums.reduce( (accumulator, currentValue) => accumulator * currentValue, 1 ); console.log(product); // 120 ``` The browser console shows the final value: ![Browser console output of JavaScript reduce() function](https://cdn.hashnode.com/res/hashnode/image/upload/v1710072423789/a7c51e1d-1d4a-4fa9-b9ea-07f90564ecaf.png) When the accumulator is an object or an array, return that same accumulator after updating it. Also, do not use `reduce()` just because you can. A short `map()` or `filter()` chain often tells the reader more about your intent. ## Choosing the right method Ask what should happen to the array. If each input needs a corresponding output, use `map()`. If some inputs should disappear, use `filter()`. If everything must become one value, use `reduce()`. You can combine them when the steps are genuinely separate. For example, `orders.filter(...).map(...)` first removes orders you do not want and then formats the remaining ones. Give each callback a useful name if the expression stops being easy to read. My caveat is that `reduce()` is not automatically the most advanced choice. I would rather read two obvious passes than decode one clever accumulator, especially when another person has to debug it later. ![Thank you graphic for JavaScript higher order functions blog](https://cdn.hashnode.com/res/hashnode/image/upload/v1710072584207/88548bcf-0b8a-42e6-a3e1-b81c1042b7b6.png) ## JavaScript Objects vs JSON: Are they same? URL: https://www.swapnoneel.site/blog/javascript-objects-vs-json Date: 2024-03-02T11:23:51.872Z Summary: JavaScript objects are live values your program can use; JSON is text for moving or storing data. Learn the syntax, type, behavior, and purpose differences. JavaScript objects and JSON look similar because JSON borrowed much of its shape from JavaScript object literals. They are still different things. An object is a value your JavaScript program can work with. JSON is text that follows a data format. You use objects while the program runs, and you often use JSON when data crosses a boundary, such as between a browser and a server. ## What a JavaScript object is An object groups related values under property names. Those values can be strings, numbers, booleans, arrays, other objects, or functions. A function stored on an object is usually called a method. ![JavaScript object concept illustration](https://cdn.hashnode.com/res/hashnode/image/upload/v1709377619396/b9dfbc39-cee3-4edc-9d95-ccb84b49a6fd.png) Think of a cup in a program. It can have a color, material, and weight, and it can have an action such as `wash()`. The object stores both the information and, when needed, the behavior associated with it. ```javascript const cup = { color: "black", design: "cylindrical", weight: 300, material: "glass", describe() { return `${this.color} ${this.material} cup`; }, }; console.log(cup.material); // glass console.log(cup.describe()); // black glass cup ``` The object exists inside the JavaScript runtime. You can read a property, call a method, add a property, or remove one. Its syntax is part of the JavaScript language. ## What JSON is JSON stands for JavaScript Object Notation, but you do not need JavaScript to read or write it. It is a text format used to represent data in a way that different languages can exchange. ![JSON data format illustration](https://cdn.hashnode.com/res/hashnode/image/upload/v1709377497454/4a07db50-35ce-476a-a183-5c8d82bdb8f1.png) JSON supports strings, numbers, booleans, `null`, arrays, and objects made from those values. It does not support functions, comments, `undefined`, or JavaScript-specific objects such as `Date` directly. ```json { "firstName": "John", "lastName": "Doe", "age": 30, "isEmployed": true, "address": { "street": "123 Main St", "city": "Anytown", "zipCode": "12345" }, "phoneNumbers": [ { "type": "home", "number": "212 555-1234" }, { "type": "office", "number": "646 555-5678" } ] } ``` Notice the quotation marks around every property name. JSON also forbids trailing commas. Those stricter rules make the text easier for another program to parse consistently. ## How objects and JSON meet When a JavaScript program needs to send an object through an HTTP request, it usually serializes the object into a JSON string. ```javascript const user = { name: "Asha", active: true, }; const body = JSON.stringify(user); console.log(body); // {"name":"Asha","active":true} ``` The result of `JSON.stringify()` is a string, not an object. You can send that string as a request body or save it in a file. When JSON comes back from a server, parse it before using it as an object. ```javascript const responseText = '{"name":"Asha","active":true}'; const parsedUser = JSON.parse(responseText); console.log(parsedUser.name); // Asha ``` `JSON.parse()` can throw a `SyntaxError` if the text is not valid JSON. If the text comes from an unreliable source, handle that failure instead of assuming the response is well formed. ## The differences that matter ### Behavior JavaScript objects can contain functions and can participate in the language's runtime behavior. JSON stores data only. If you stringify an object with a method, the function is left out. ```javascript const account = { name: "Asha", greet() { return `Hello, ${this.name}`; }, }; console.log(JSON.stringify(account)); // {"name":"Asha"} ``` ### Syntax JavaScript object literals can use unquoted property names when they are valid identifiers, single or double quotes for strings, trailing commas, comments around the code, and computed properties. JSON requires double-quoted property names and string values, and it does not allow comments or trailing commas. ### Types JavaScript has values such as `undefined`, `Date`, `Map`, `Set`, `BigInt`, and functions. JSON has a smaller set of data types. During serialization, `undefined` and functions may disappear from an object, while a `Date` is converted to a string through its serialization behavior. ### Purpose Objects help your JavaScript code organize and manipulate live data. JSON helps systems exchange a snapshot of that data. A JSON response becomes an object only after your code parses it. ## A small comparison | JavaScript object | JSON | | --------------------------------------------------- | ------------------------------------------------------ | | A runtime value in JavaScript | Text that follows a data format | | Can contain methods | Cannot contain functions | | Property names may be unquoted in an object literal | Property names must use double quotes | | Can use JavaScript-only values | Supports a smaller set of data types | | Read directly by JavaScript code | Parsed with `JSON.parse()` before normal object access | ## Which one should you use Use a JavaScript object while your application is reading, changing, or passing data around in memory. Use JSON when you need a portable text representation for an API response, request body, configuration file, or saved state. Do not call `JSON.stringify()` just because a value looks like JSON. First check the boundary. If another function in the same program expects an object, converting it to text only creates extra work and can remove values that JSON cannot represent. The sentence I keep coming back to is simple: an object is data your program can use, while JSON is text your program can exchange. Once you separate those two jobs, the similar-looking braces stop being confusing. ![Thank you graphic for JavaScript Objects vs JSON blog](https://cdn.hashnode.com/res/hashnode/image/upload/v1709377456629/8e775196-cf5e-4e28-bdc1-8bf7d419813f.png) ## Designing Machine Learning Workflows in Python URL: https://www.swapnoneel.site/blog/designing-machine-learning-workflows-in-python Date: 2024-02-02T16:07:14.579Z Summary: A machine-learning workflow turns data into features, trains and evaluates a model, and saves the result. Build that process in Python with Fashion MNIST and IMDb examples. Machine learning code is rarely just a model. You also need data that the model can read, a repeatable way to turn that data into features, an evaluation that matches the problem, and a way to save the result for later. That collection of steps is a machine learning workflow. Python is useful here because the same language can handle data loading with Pandas, numerical work with NumPy, classical models with scikit-learn, and neural networks with TensorFlow or PyTorch. The order matters. If you clean the test data using information from the training data, your score becomes too optimistic. If you choose a metric that does not match the cost of an error, a high score may hide a bad product decision. ## Start with the question Before opening a notebook, write down what the model should predict and what a useful prediction would change. Predicting whether a review is positive is a classification problem. Predicting the price of a house is a regression problem. Grouping similar customers without labels is a clustering problem. This decision affects the target column, the model family, and the metric. It also tells you what kind of error matters. In a fraud system, missing a suspicious transaction may matter more than reviewing an extra legitimate one. In a delivery estimate, the size of the error may matter more than whether the answer is on one side of a threshold. ## Build the workflow in order ### Prepare the data The model cannot learn from a messy table in the same way a person can. You need to decide which rows are usable, which column is the target, how missing values are handled, and how categories or text become numbers. The test set must remain untouched while you make those choices. It is meant to imitate data the model has never seen. This example shows the shape of a classification workflow. It assumes `data.csv` contains numeric feature columns and a column named `target`. ```python import numpy as np import pandas as pd from sklearn.model_selection import train_test_split from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, precision_score, recall_score dataset = pd.read_csv("data.csv") target_column = "target" # Remove rows that cannot be used for the target decision. dataset = dataset.dropna(subset=[target_column]) X = dataset.drop(columns=[target_column]) y = dataset[target_column] # Keep the test set separate until the final evaluation. X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y, ) # Fit preprocessing only on the training data by putting it in a pipeline. model = make_pipeline( StandardScaler(), LogisticRegression(max_iter=1000), ) model.fit(X_train, y_train) y_pred = model.predict(X_test) print("Accuracy:", accuracy_score(y_test, y_pred)) print("Precision:", precision_score(y_test, y_pred, zero_division=0)) print("Recall:", recall_score(y_test, y_pred, zero_division=0)) ``` The file and column names are assumptions, so change them for your dataset. The important part is the boundary: split first, fit transformations on the training data, and use the test data only for the final check. ### Create useful features A feature is an input the model can use. Raw data is not always arranged in a useful form. A timestamp may become hour and weekday columns. A sentence may become word features. A table with hundreds of related numeric columns may need dimensionality reduction. Principal component analysis, or PCA, is one way to reduce numeric features to a smaller representation. Fit it on the training data, then apply the already-fitted transformation to the test data. ```python from sklearn.decomposition import PCA pca = PCA(n_components=2, random_state=42) X_train_pca = pca.fit_transform(X_train) X_test_pca = pca.transform(X_test) ``` PCA is not automatically a good feature choice. Two components are easy to plot, but they may discard information that the model needs. Compare the reduced version with the original features rather than assuming fewer columns means a better model. ### Choose and compare models Model selection is a comparison against the problem, not a contest for the most complicated algorithm. A linear model can be easier to inspect and may work well when the relationship is simple. A tree-based model can capture different kinds of boundaries but may need its own tuning. Cross-validation gives you several training and validation splits instead of trusting one lucky split. Use the same scoring rule for each candidate. ```python from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score logistic_model = LogisticRegression(max_iter=1000) forest_model = RandomForestClassifier(random_state=42) logistic_scores = cross_val_score( logistic_model, X_train_pca, y_train, cv=5, scoring="accuracy", ) forest_scores = cross_val_score( forest_model, X_train_pca, y_train, cv=5, scoring="accuracy", ) best_model = ( forest_model if np.mean(forest_scores) > np.mean(logistic_scores) else logistic_model ) print("Logistic regression:", np.mean(logistic_scores)) print("Random forest:", np.mean(forest_scores)) ``` The comparison above uses the PCA features from the previous step. In a real project, put PCA inside a scikit-learn pipeline before cross-validation so each fold learns its own transformation. That prevents information from the validation fold leaking into training. ### Train and evaluate Training is where the model learns parameters from the training set. Evaluation asks how well those learned parameters work on data held back from that process. ```python best_model.fit(X_train_pca, y_train) y_pred = best_model.predict(X_test_pca) accuracy = accuracy_score(y_test, y_pred) precision = precision_score(y_test, y_pred, zero_division=0) recall = recall_score(y_test, y_pred, zero_division=0) print("Accuracy:", accuracy) print("Precision:", precision) print("Recall:", recall) ``` Accuracy is the fraction of correct predictions. Precision asks how many predicted positives were actually positive. Recall asks how many real positives the model found. Choose the metric before looking for the best score, otherwise it is easy to optimize for a number that does not describe the product. ### Save the model Training can be expensive, and a deployed application needs the learned model without retraining it for every request. Save the fitted model together with every preprocessing step it needs. ```python import joblib joblib.dump(best_model, "model.pkl") ``` If the feature transformation is separate from the model, save that transformation too. A production prediction must receive data in the same shape and scale used during training. ## Example with Fashion MNIST Fashion MNIST is a useful small example because every image has the same shape and the dataset already comes with training and test splits. The model below classifies the images into ten categories. ```python import numpy as np import tensorflow as tf from tensorflow import keras fashion_mnist = keras.datasets.fashion_mnist (X_train, y_train), (X_test, y_test) = fashion_mnist.load_data() # Pixel values arrive as integers from 0 to 255. X_train = X_train.astype("float32") / 255.0 X_test = X_test.astype("float32") / 255.0 model = keras.Sequential([ keras.Input(shape=(28, 28)), keras.layers.Flatten(), keras.layers.Dense(128, activation="relu"), keras.layers.Dense(10, activation="softmax"), ]) model.compile( optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"], ) model.fit(X_train, y_train, epochs=10, validation_split=0.1) test_loss, test_accuracy = model.evaluate(X_test, y_test, verbose=0) print(f"Test loss: {test_loss:.4f}") print(f"Test accuracy: {test_accuracy:.4f}") model.save("fashion_mnist_model.keras") ``` The dataset is loaded with the [keras.datasets.fashion](http://keras.datasets.fashion)\_mnist module. The images are stored as 28-by-28 pixel arrays, and dividing by 255 puts each pixel into a small numeric range. The `Flatten` layer turns each image into one vector. The hidden dense layer learns combinations of those pixel values, and the final layer produces ten class scores. `sparse_categorical_crossentropy` fits integer labels such as `0` through `9` without requiring you to convert them into one-hot arrays. The model trains with [model.fit](http://model.fit)() on the training images and checks the test images only after training. When the code is run through the terminal, the model is trained, evaluated, and the test loss and accuracy are printed, as shown below: ![Terminal output of Fashion MNIST model training and evaluation](https://cdn.hashnode.com/res/hashnode/image/upload/v1706888496190/80a5fff8-8809-43d7-b8cc-8459949edd5c.png) ## Example with IMDb sentiment Text needs a different feature step. A logistic regression model cannot read raw sentences, so `TfidfVectorizer` turns words into numbers based on how often they appear in a review and how unusual they are across the dataset. ```python import joblib import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score from sklearn.model_selection import train_test_split df = pd.read_csv("imdb_reviews.csv") X = df["review"] y = df["sentiment"] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y, ) vectorizer = TfidfVectorizer() X_train_vectors = vectorizer.fit_transform(X_train) X_test_vectors = vectorizer.transform(X_test) model = LogisticRegression(max_iter=1000) model.fit(X_train_vectors, y_train) y_pred = model.predict(X_test_vectors) accuracy = accuracy_score(y_test, y_pred) print(f"Accuracy: {accuracy:.4f}") joblib.dump(model, "sentiment_analysis_model.pkl") joblib.dump(vectorizer, "vectorizer.pkl") ``` The dataset is loaded with [pd.read](http://pd.read)\_csv() into a DataFrame. In this example, `review` contains the text and `sentiment` contains the label. The split happens before fitting the vectorizer, which keeps vocabulary information from the test set out of training. The [model.fit](http://model.fit)() call learns the relationship between the training reviews and their labels. The `model.predict()` call then produces labels for reviews it did not see during training. Finally, `joblib.dump()` saves both the classifier and the vectorizer because the deployed application needs to transform new text in the same way. The Keras example uses [model.save](http://model.save)() for the same reason. When the code is run through the terminal, the model is trained, evaluated, and the accuracy is printed, as shown below: ![Terminal output of sentiment analysis model training and accuracy](https://cdn.hashnode.com/res/hashnode/image/upload/v1706889095784/94b6cb10-93a0-4b56-8e9d-0cd704d94764.png) ## Moving from a notebook to an application Keep a record of the data columns, preprocessing steps, model version, and evaluation split. A saved model without the code that prepared its inputs is hard to trust and harder to reproduce. Before deployment, test the prediction path with new examples and invalid input. Watch for missing columns, unexpected categories, empty text, and values outside the range used during training. Log model inputs carefully and avoid storing private data unless you have a clear reason to do so. Once the model is running, monitor the inputs and the outcomes you can observe. Data changes over time, and a score from last month cannot tell you whether today's requests still look like the training data. ## What to remember A machine learning workflow is a chain. Start with a clearly defined prediction, prepare the data without leaking the test set, create features that represent the problem, compare models with a meaningful metric, evaluate on held-out data, and save every piece needed for prediction. The code is only one part of the job. The decisions around the code determine whether the final number tells you anything useful. I still find the data split and metric choice easier to get wrong than the model import, which is probably why they deserve more attention than the flashy part. For more posts, you can follow me on [Twitter (swapnoneel123)](http://twitter.com/swapnoneel123). You can also check my [GitHub (Swpn0neel)](https://github.com/Swpn0neel) for projects. ![Machine learning model evaluation matrix and performance metrics](https://cdn.hashnode.com/res/hashnode/image/upload/v1706889699251/e0331511-ab42-4e0c-997c-5cbe529b3888.png) ## My First year in Tech URL: https://www.swapnoneel.site/blog/my-first-year-in-tech Date: 2023-10-24T13:00:31.327Z Summary: Exactly one year back from now, I got into University for my Bachelor's degree in Computer Science and Engineering. And it was then, I actually got introduced to the Tech world. Previously, I had some coding experience in both Java & Python and had a... Exactly one year back from now, I got into University for my Bachelor's degree in Computer Science and Engineering. And it was then, I actually got introduced to the Tech world. Previously, I had some coding experience in both Java & Python and had a proper knowledge of MySQL. In this one year, I have learned and experienced quite a few things. So, I thought it would be the perfect time to document my journey with the timelines and also share some of my plans for the next year. This may help the beginners in starting their journey and will definitely motivate me for my upcoming journey. So let's begin… ## The Journey ### October, 2022 As this was my first month of college, I focused on my academics a bit and attended classes regularly. Besides that, as I had a bit of coding knowledge, I decided to start learning Web Development. I followed the free course from [Code with Harry](https://www.youtube.com/@CodeWithHarry), which is available on YouTube. And by the end of that month, I was mostly done with HTML. It didn't take me more than two weeks to learn it as it was extremely simple and beginner-friendly. ### November, 2022 This was an interesting month for me, because it was when I got introduced to CSS, and the sheer amount of styling that I can do with it amazed me 🤩. I continued with Code with Harry and started creating some small projects including a form page for School admission, a basic site for a fictional shoe brand, etc. ### December, 2022 I decided to enhance my skills in HTML and CSS more before moving on to JavaScript, so I took a course from Coursera, which was taught by Meta. Following that, I started and completed learning Bootstrap from there. And the most interesting and a bit disappointing thing happened during this month: > The interesting thing is, that I got my first Tech freelancing gig, which consisted of building a few small projects using Bootstrap and the APIs provided by the client, for showcasing the service of their small start-up. And, it was a 15K rupees gig. Now, the disappointing part is, that I couldn't complete the gig because some part of it was extremely out of my league. But they paid me 8K rupees for the work I had done. **The most important thing I learned was that real-life development is all about googling, pasting codes from StackOverflow, and learning while doing so…** Took full advantage of my Winter vacation, to start learning some advanced concepts of Python and finally got started with JavaScript!! ### January, 2023 This is that single month where I did a lot of "**first-time**" things and learned & got started with them. Continued learning JavaScript from both Code with Harry and Coursera. And, I started building some small projects using my newly learned concepts from Python. Now with the skills I have learned so far, I was thinking of putting them to the test by attending a Hackathon. So, I grabbed one of my classmates and a 2nd-year guy from another college. Our small team had a diverse skill set, and we decided to participate in "**Treasure Hacks 3.0**". Our project was "Lab for All" (Website link: [https://labforall.vercel.app](https://labforall.vercel.app), GitHub link: [https://github.com/Swpn0neel/treasure-hacks-labforall](https://github.com/Swpn0neel/treasure-hacks-labforall)) > We planned to make a project on the Ed-Tech field and the idea was to create a platform where the users can get assistance, particularly in their lab assignments, from video lectures to notes, and viva questions to 3D simulations. > > While working on the project, I got to know about how you have to make the site design before starting to code, and that attracted me a lot and planted the seed inside me to start designing in Figma, which I planned to learn in the near future. > > Strangely enough, till now I haven't learned about Git and GitHub. But I had to grasp it now for the obvious reason of collaboration in my Hackathon project. And it was a significant bump in my Tech learning journey. > > Also, while doing the project I had to learn Firebase as it was needed for data storing and user authentication, and it was my part to complete that job. The best part is, we completed the project within the time limit and implemented all the planned features. And to our amazement, **we won the Hackathon being the second runner-up**. Also, this month, I wrote my first blog at Hashnode on Python and also got active on [Twitter (now X)](https://twitter.com/swapnoneel123), and started posting content on both mediums. ### February, 2023 Our first-semester final exam happened this month and was stretched over a period of 20 days. And later, when the results came out, I topped my batch with an SGPA of 9.33. This month, I also focused on my blogging and wrote around 8-9 articles. Also, I started working on my [personal portfolio website](http://swapnoneel.vercel.app) and completed it. It took just over two weeks to do that. Learned a bit of React and JavaScript while building my portfolio, and also worked on file handling and its modules in Python. [Check out the tweet/post](https://twitter.com/swapnoneel123/status/1630930494840864768) ### March, 2023 After a lot of grinding web development for some months, I decided to switch the gear a bit and decided to start doing DSA. Now for doing that, I rarely used any guide or video tutorials, but relied on solving more and more Leetcode problems (you can check my [Leetcode profile, here](https://leetcode.com/Swapnoneel/)) based on my previous knowledge of arrays, strings and recursion. By doing so, I solved just over 30 problems and approached around 100 problems in that venture. Also, I started learning UI/UX design and gained a huge interest in that. Followed Jesse Showalter, Design Course and Payton Clerk on YouTube to get knowledge about designing. I used Figma for the design purpose!! Besides these, I completed the "7 days, 7 blogs" challenge on Hashnode and mostly shared my Python knowledge there. And also, explored a lot of Python libraries mainly about text and image analysis. ### April, 2023 Continued my DSA journey and solved over 20+ questions based on String Builder, Array List and Arrays. Also, I decided to go for another Hackathon along with two of my classmates. It was one of the **MLH hacks**, called "**Hack Around the World 2.0**". Our project was "**The Magnificent Seven**" (Website link: [https://magnificent-seven.vercel.app](https://magnificent-seven.vercel.app), GitHub link: [https://github.com/Swpn0neel/TheMagnificentSeven](https://github.com/Swpn0neel/TheMagnificentSeven))We decided to use Three.js, Svelte and Tailwind CSS for the project. That's why, I started learning Svelte and Tailwind CSS. Also, I created the Figma design of the site. This was a much smaller scale project than the previous one but cramped most of our technical skills into it. And again, to our amazement, we managed to be the **Winner of this Hackathon**. ### May, 2023 Paused DSA for a bit, but started learning C as it was a part of my college curriculum. Also, I delved more into designing and started mastering animations and transitions in CSS and decided to build some small-scale projects using that. In that venture, I created around 5-6 projects that focused mainly on uniqueness and smooth animations. Check them out here: [https://github.com/Swpn0neel/Web-Development-Projects](https://github.com/Swpn0neel/Web-Development-Projects) And the most important thing that happened at the end of the month. Previously, I mentioned that I wrote a number of blogs regarding Python in the months of February and March. Now, they got noticed by **Tutorials Point**, and I got a part-time job offer from them, whose first contract was to deliver an advanced Python course for their platform. I accepted the offer and started working for them. ### June, 2023 This was a really special month, because I only earned, earned and earned in this one. Aside from Tutorials Point, I got some really good freelancing offers too: I created the UI/UX Design and wrote SEO-optimized content for some medium-scale businesses. Delivered some copywriting and technical writing jobs. Also, this was the month of our second-semester exam. I was extremely loose academically this semester and didn't have high hopes about scoring. But still, some fruitful one-nighters helped me to get an SGPA of 9.14. ### July, 2023 Took the after-semester break to my advantage and again went for an earning streak with various freelancing job offers. For some time I hadn't attended any hackathons, so in the middle of the month, our same team from MLH Hacks decided to attend our first offline hackathon and we chose Eastern India's largest Hackathon, Hack 4 Bengal 2.0 for that. Everything was going nicely there, but one of our teammates faced health issues, and that's why we had to return to our home before the Hackathon ended. It was a disappointing failure for us. But for me personally, it was extremely fruitful from an earning standpoint. ### August, 2023 The month started with me hosting an introductory session to Open-Source at my University for my fellow peers, along with two other folks from my batch. It was my first mentoring and public speaking session. Also, an important thing that caught my eye was that, I haven't learned a lot of things personally (except Python, most of my Freelancing works were dependent on that and I learned a lot of Python libraries during that time), as I was majorly focusing on earning. Also, I haven't touched DSA for some months. So, I again started Leetcoding at the end of this month. Previously, I had completed 50+ problems, I added 10+ to that number this month. ### September, 2023 Rejected all Freelancing offers this month and went hard on Leetcoding. I solved more than 90 problems this month, ranging from Linked List and Prefix Sum to Siding Window and Recursion problems. Also, I started preparing for the Smart India Hackathon '23. Involved two juniors in our team so that they can have their first Hackathon experience and learn along the way. As part of the national hackathon, we have to qualify for the internal Hackathon of our University, MAKATHON '23. We qualified with flying colors taking the first position in the Hackathon. ### October, 2023 Decided to make some complex projects related to Web Development and prepared some web designs on Figma for the same, while learning a bit more about UI/UX Designing in general. Started taking weekly mentorship sessions for my juniors, so that they could learn at a better pace and not get distracted while being focused on the ultimate goal of upskilling themselves. ## Bonus I have mentioned almost everything here. But I want to mention some things that weren't mentioned in the timeline. - Python and its vast library have always been with me throughout my learning journey and earned me the majority of my bucks through freelancing. - I was able to purchase an ASUS TUF A15 laptop all by myself through freelancing ventures, and also manage a lot of my personal expenses besides making a little bit of savings for myself. - Since January, I've been active on Twitter and made a family of over 500 folks. - College academics were never my priority and I tried to ignore it as much as possible and gave the least possible amount of time to it. - Although I started my journey with the help of video tutorials, as time passed I majorly relied on official documentation and blogs because of their simplicity and least time requirement. - Other than the mentioned stuff, I had to learn quite a few other things like API management, handling ML models, and many more for project requirements. It's impossible for me to mention all of them in a single blog 😅. ## My plans for the Second year So far, I have completed 170+ Leetcode problems, and my target moving forward is to cover all the DSA concepts in the 2nd year and at least reach the 600+ problems mark on Leetcode. Also, I'll start attending coding contests to put my skills to the test. I will start delving into AI/ML and would like to get a solid grip on it by October 2024. Along with that, I plan to make some SaaS products with my knowledge of AI/ML and Web Development. Also, I'll attend more and more hackathons, because the learning opportunity from there is just undeniable. And, I would start focusing on subjects like System Design and Operating Systems. Plus, I would be actively looking forward to grabbing any internship opportunities. And lastly, I would continue mentoring my fellow juniors and would start focusing on making Open-Source contributions to renowned organizations. ## Conclusion And that's a wrap!! But if you have any queries regarding my journey or any suggestions for my future, I would love to hear that from you in the comments. Also, as a closing note, I would like to share my resume which I have created recently based on my progress so far, you can check out [my resume document](https://docs.google.com/document/d/1c931FkeAugaefH0LcY2Irap0UOp4D9cLpyAgwjOt1ao/edit?usp=sharing). And if you want to stay connected with me, you can follow me here or on my other social handles. Thank You for reading about my journey, have a nice day ahead!! ![First year in tech reflection banner](https://cdn.hashnode.com/res/hashnode/image/upload/v1698152151751/048427f8-d108-47e0-9f9d-79b1596fa335.png) ## Best Frameworks for Web Development Compared URL: https://www.swapnoneel.site/blog/best-frameworks-to-use-for-web-development Date: 2023-07-28T15:04:47.555Z Summary: React, Vue, Angular, Express, Solid, Next, and Svelte solve different web problems. Compare their trade-offs and choose a framework without chasing hype. You can lose more time choosing a web framework than writing the first version of the site. A search for the "best" option gives you a pile of rankings, benchmark screenshots, and very confident opinions. None of that tells you what your project actually needs. So start with the boring question: what has to happen when someone visits the page? Maybe the server returns a document and a few assets. Maybe the browser keeps updating a dashboard as the user clicks around. Maybe the application needs an API, authentication, and a team-wide way to keep hundreds of files organized. A framework is a set of decisions around those jobs. It may give you components, routing, data loading, project folders, build tools, or server code. A library usually solves one part and leaves more of the surrounding decisions to you. That distinction matters here because React and SolidJS are primarily interface libraries, while Next.js and Angular provide a wider application structure. Express.js is a Node server framework, so it belongs on the server side of the conversation. ## The questions that narrow the choice Do not begin with popularity. Begin with the shape of the work. For a personal landing page, a framework may be unnecessary. A small amount of HTML and CSS can be easier to deploy and easier to understand six months later. For a dashboard with filters, shared state, and several screens, components and routing start paying for themselves. Now ask where the first HTML should come from. A browser-rendered application can load a small shell and build the page with JavaScript. A server-rendered application can send useful HTML in the first response, then add browser behavior afterward. Content-heavy pages, documentation, and stores often care about that first response more than a private admin screen does. Also ask who will maintain the decisions. A flexible stack lets an experienced team choose exactly what it wants. It gives a new team more opportunities to choose five different patterns for the same problem. An opinionated framework can feel restrictive on day one and calming on day one hundred. My short checklist is simple: 1. Is this mostly static content, a browser application, a server, or a mixture? 2. Do you need server rendering or an API built into the same project? 3. How much structure will make the next change easier? 4. Does the team already know one of these tools well? With that in mind, the names below stop being a popularity contest. ## React React is a JavaScript library for building user interfaces. Its central idea is the component: a piece of markup and behavior that you can reuse inside a larger page. That sounds small, but it changes how you work. A product card can receive a product as a prop, render its title and price, and appear in several screens without copying the markup. A form can keep its input state in one place. A page can be assembled from those smaller parts. ![React framework logo](https://ms314006.github.io/static/b7a8f321b0bbc07ca9b9d22a7a505ed5/97b31/React.jpg) React is a strong fit for interactive applications and shared component libraries. It also has a large collection of surrounding tools, which means unusual problems often have several existing solutions. That same freedom is the part I would warn a beginner about. React does not, by itself, choose your router, data-fetching approach, form library, or folder layout. Two React projects can feel like different ecosystems. Pick it when you want that room and are willing to make the decisions. If you want one official path from page to production, React alone will leave you with homework. ## Vue.js Vue.js is a front-end framework that can sit inside an existing page or support a complete application. Its single-file components keep the template, script, and styles close together, which makes the first example easy to follow. ![Vue.js framework logo](https://segwitz.com/wp-content/uploads/2021/06/vuejs-development-malaysia.jpeg) Vue is a good choice when you want component-based development without a huge amount of ceremony. You can add a small interactive widget to an existing page, then use the same component model for a larger application later. The trade-off is ecosystem size. React has more packages, tutorials, and answers for odd edge cases. Vue still covers the usual work, but you may need to make more of the solution yourself when the problem gets unusual. For a small project where I want to scan the code quickly, I would choose Vue before React. That is a preference, not a law of nature. ## Angular.js Angular is a full front-end framework maintained by Google. It gives you a defined way to write templates, inject services, configure routes, and organize an application. TypeScript is part of the normal setup, so types arrive with the rest of the framework rather than as an optional extra. ![Angular framework logo](https://www.searchenginejournal.com/wp-content/uploads/2019/04/the-seo-guide-to-angular.png) That structure suits large teams that want similar patterns across the codebase. Dependency injection gives services a clear place to live, and the project conventions make it easier to find the expected home for a route or feature. Angular asks you to learn more before the first feature feels comfortable. TypeScript, decorators, templates, services, and the application structure arrive together. I would not choose it for a small page unless the team already works in Angular. For a large application with an Angular team, the rules are the reason to choose it. ## Express.js Express.js runs on Node.js and handles server-side work. A request enters the server, passes through middleware, reaches a route handler, and leaves as a response. That makes Express useful for APIs, small web servers, and the backend behind a React or Vue application. ![Express.js framework logo](https://miro.medium.com/v2/resize:fit:805/0*m1VOQP0FtcQufLgw.png) Express is intentionally small. You add the middleware you need for JSON parsing, authentication, logging, or database access instead of receiving a complete application structure on day one. That flexibility can turn into a pile of decisions. Express will not choose your folder layout or stop every route from becoming a giant function. Pick it when you want a thin Node server and are comfortable designing the rest. Also, do not call it a front-end framework just because it appears in the same web stack. ## Solid.js SolidJS is a JavaScript library for building interfaces with fine-grained reactivity. When a piece of state changes, Solid can update the part of the page that reads that state instead of rerunning a whole component tree in the same way a virtual-DOM approach does. ![SolidJS framework logo](https://www.solidjs.com/og.jpg) Solid's model is attractive when you care about small updates and want to write components with familiar JavaScript and JSX. The price is a smaller ecosystem. A React answer that appears in the first search result may require more reading and experimentation in Solid. I like the model, but I would not make Solid the default recommendation for a beginner who needs the biggest pool of examples and packages. Choose it when the update model or the authoring style solves a real problem, not because a benchmark screenshot looks nice. ## Next.js Next.js is a framework built around React. It adds routing, server rendering, static generation, and server-side features to the component model. A page can send useful HTML before the browser has built every interactive part, which is the distinction that matters in practice. ![Next.js framework logo](https://images.ctfassets.net/c63hsprlvlya/IacLLeOBR5WCvdCPqKuff/6860b5cc464c4f54703a2befa3f706b4/nextjs3.webp) That makes Next.js a natural fit for blogs, documentation, stores, and applications where the first response matters. It also gives you server-side features and API routes, although a project can still use a separate backend. The trade-off is mental overhead. You have to know what runs in the browser, what runs on the server, and when data is fetched. For a plain client-side application, that can be more machinery than the page needs. For a content-heavy application, the same machinery can save you from assembling the pieces yourself. ## Svelte Svelte moves much of the framework's work to the build step. You write a component with HTML, CSS, and JavaScript, and the compiler turns it into JavaScript that updates the page directly. Svelte does not need a virtual DOM for that update model. ![Svelte framework logo](https://codemonk.in/blog/content/images/2022/03/Svelte-Feature-Image.png) Svelte is pleasant when you want components that look close to the HTML they produce. It also works well for small widgets embedded in an existing page, where a large application framework would feel like too much. The caveat is the smaller ecosystem. Svelte's syntax can feel direct, but you may have fewer tutorials and integrations to choose from. That is a reasonable trade if the generated output and authoring style matter more than having the biggest package catalogue. ## So which one should you choose Choose React when you want the largest ecosystem and do not mind deciding how the rest of the application fits together. Choose Vue when you want a gentler template-driven start. Choose Angular when a team needs a complete structure and is willing to learn the framework's rules. Choose Next.js when React needs server-rendered or static pages. Choose Express when the job is an API or a Node server. Choose SolidJS or Svelte when their rendering models match a real requirement and the smaller ecosystems are acceptable. My winner for a general front-end learning path is React because its component model appears in so many kinds of projects. The honest downside is that a beginner can spend an afternoon picking routers and state libraries instead of building the page. For a small site, I would personally choose Vue or Svelte and keep the setup quiet. But that's just me, and your workflow might be different. If you are stuck, build the smallest version of the project first. The right choice usually becomes clearer after you know whether the hard part is the interface, the server, the data, or the team workflow. For more information, follow me on [Twitter @swapnoneel123](http://twitter.com/swapnoneel123) where I share more such content through my tweets and threads. You can also check my [GitHub(username: Swpn0neel)](https://github.com/Swpn0neel) to see my projects. ![Grammarly writing assistant banner](https://contenthub-static.grammarly.com/blog/wp-content/uploads/2019/02/bmd-4584.png) ## DevBytes: Short Coding News URL: https://www.swapnoneel.site/blog/devbytes-short-coding-news Date: 2023-03-28T15:12:34.057Z Summary: What is DevBytes? DevBytes is a comprehensive and user-friendly programming and technology news mobile application that is designed to cater to your specific interests. With its advanced algorithms, it sifts through vast amounts of information to bri... ## What is DevBytes? DevBytes is a comprehensive and user-friendly programming and technology news mobile application that is designed to cater to your specific interests. With its advanced algorithms, it sifts through vast amounts of information to bring you the latest and most helpful tech news that is tailored to your domain of interest. Unlike other tech news applications, DevBytes is highly efficient and values your time, delivering the information you need in a concise and precise manner, within a maximum of 64 words. ## Main Features DevBytes has numerous standout features that distinguish it from other offerings. Let's take a closer look at each one to understand their significance. ### Your Content, Your Choices The platform has an innovative design that includes a customizable interface, allowing users to personalize their news feed according to their preferences. Users can receive the latest breaking news, important updates, and innovative highlights through the platform's personalized news feed. Unlike other apps, this platform's approach is streamlined, ensuring that users only receive news that is relevant to them, which saves them time and effort. ### Everything within 64 words DevBytes prides itself on valuing users' time and delivering news in a fast and efficient manner. We stand out by presenting a concise 64-word news story that offers precise, easily understandable information without compromising quality or accuracy. Our focus is on streamlining the news experience by selecting relevant stories and prioritizing brevity and clarity. This means you can stay up-to-date on the latest without having to sift through unnecessary details. Our skilled team of journalists and editors work diligently to craft each news story with care and precision, ensuring that every word counts towards delivering the most essential information. Whether you're interested in the latest tech updates, new product launches, or global events, DevBytes is committed to providing you with the information you need, when you need it, in a format that respects your time and intelligence. ### "Jobs" at Hand Take advantage of amazing opportunities with DevBytes! Keep yourself informed about the newest job vacancies from various global companies that align with your area of expertise, easily accessible in a single feed. ## Additional Features - **Code Snippets:** Easily learn and apply coding tips and tricks by reading and executing the attached code on the go. - **Crypto Watch**: Stay updated on your preferred cryptocurrencies at all times using the convenient on-the-go feature. - **Deals and Discounts**: Take advantage of amazing deals and promotions on fantastic products or subscriptions. - **Product of the Day**: Discover handpicked productivity tools on your feed. ## Conclusion DevBytes is an incredible app that I've had the pleasure of using. What makes it stand out is its focus on putting its users first by providing all the amenities they may require. The app features a sleek design and a minimalistic appearance, ensuring users have the best possible experience. In today's era of rapid technological advancements, apps like DevBytes are the optimal choice. If you've ever wondered how I manage to stay up-to-date with the latest tech trends, let me share a little secret with you - it's all thanks to DevBytes. If you have enjoyed reading this blog, I recommend checking out the app available on PlayStore. You can find the link to download it here: [DevBytes](https://play.google.com/store/apps/details?id=com.candelalabs.devbytes&utm_source=app) ![Thank you blackboard illustration for DevBytes news blog](https://c0.wallpaperflare.com/preview/726/785/255/blackboard-close-up-frame-gratitude.jpg) ## Inheritance in Python with Examples URL: https://www.swapnoneel.site/blog/inheritance-in-python Date: Tue, 07 Mar 2023 15:49:19 GMT Summary: Python inheritance lets a class reuse or replace behavior from a parent. This guide covers single, multiple, multilevel, hybrid, and hierarchical inheritance with examples. ## Start with the relationship Inheritance lets one class begin with behavior from another class. The new class is the child or subclass, and the existing class is the parent or base class. ```python class Animal: def eat(self): print("eating") class Dog(Animal): pass dog = Dog() dog.eat() ``` Dog gets eat from Animal, so the last line prints eating even though Dog has no method of its own. Python looks in Dog first, then follows the class's method resolution order until it finds the requested name. That sounds simple, and it is. The design question is harder: should Dog really be treated as an Animal? Inheritance fits an "is a" relationship. A Dog is an Animal. A Dog has a Collar, so a collar would usually be another object stored on the dog, not a parent class. That distinction prevents a lot of awkward hierarchies. ## Single inheritance Single inheritance gives a class one direct parent. It is the easiest form to read because the lookup path has one branch. ![Single inheritance diagram](https://media.geeksforgeeks.org/wp-content/uploads/20200108135809/inheritance11.png) Here, Dog reuses the name setup from Animal and replaces the generic sound: ```python class Animal: def __init__(self, name): self.name = name def make_sound(self): print("A sound comes from the animal.") class Dog(Animal): def make_sound(self): print("Bark!") dog = Dog("Max") print(dog.name) dog.make_sound() ``` Dog does not define **init**, so Python finds Animal.**init** and uses it. Dog does define make_sound, so its method wins over the parent version. The output is: ```text Max Bark! ``` Override a method when the child has a genuinely more specific version of the same behavior. If the child merely needs to add a little work, call the parent implementation with super() rather than copying its body. ## Multiple inheritance Multiple inheritance gives one class more than one direct parent. Small mixins are the least surprising use case: each parent supplies a separate behavior, and the child combines them. ![Multiple inheritance diagram](https://media.geeksforgeeks.org/wp-content/uploads/20200108144424/multiple-inheritance1.png) ```python class Swimmer: def swim(self): print("Swimming") class Runner: def run(self): print("Running") class Triathlete(Swimmer, Runner): pass athlete = Triathlete() athlete.swim() athlete.run() print(Triathlete.__mro__) ``` The class gets swim from Swimmer and run from Runner. The final print shows the order Python searches, ending with object. If both parents define the same method, Swimmer wins in this example because it appears first in the class definition. That does not mean you should pick a parent order at random. The method resolution order is part of the behavior. Also, parent initializers need care. Calling ParentA.**init** and ParentB.**init** manually can work, but it becomes fragile when a third class enters the hierarchy. Cooperative classes use super() and accept compatible arguments so Python can walk the whole MRO once. ## Multilevel inheritance Multilevel inheritance creates a chain. One class extends a parent, and another class extends that child. It is useful when each level describes a real increase in specialization. ![Multilevel inheritance diagram](https://media.geeksforgeeks.org/wp-content/uploads/20200108144705/Multilevel-inheritance1.png) ```python class Animal: def __init__(self, name): self.name = name def show_details(self): print(f"Name: {self.name}") class Dog(Animal): def __init__(self, name, breed): super().__init__(name) self.breed = breed def show_details(self): super().show_details() print(f"Breed: {self.breed}") class GoldenRetriever(Dog): def __init__(self, name, color): super().__init__(name, "Golden Retriever") self.color = color def show_details(self): super().show_details() print(f"Color: {self.color}") dog = GoldenRetriever("Max", "Golden") dog.show_details() ``` The call to GoldenRetriever.show_details travels through all three implementations because each method does its own work and then calls super(). The output is: ```text Name: Max Breed: Golden Retriever Color: Golden ``` Notice that super() does not mean "call my immediate parent by name" in a simple fixed sense. It follows the MRO from the current class. That detail becomes valuable in cooperative multiple inheritance, and it is also why direct calls such as Animal.show_details(self) can make a hierarchy harder to extend. ## Hybrid inheritance and the MRO Hybrid inheritance combines shapes. A common example has two branches that share a base class, followed by a child that inherits from both branches: ![Hybrid inheritance diagram](https://media.geeksforgeeks.org/wp-content/uploads/Hybrid-Inheritance.png) ```python class Person: def __init__(self, name): self.name = name class Student(Person): def study(self): print(f"{self.name} is studying.") class Athlete(Person): def train(self): print(f"{self.name} is training.") class StudentAthlete(Student, Athlete): pass person = StudentAthlete("Mina") person.study() person.train() print(StudentAthlete.__mro__) ``` StudentAthlete can use study and train, and the inherited Person initializer supplies name. The MRO prevents Person from being visited twice in the diamond-shaped path. That is the part you need to understand before adding parent initializers or methods with the same name. If each branch has its own setup, make the constructors cooperate: ```python class Person: def __init__(self, name, **kwargs): super().__init__(**kwargs) self.name = name class Student(Person): def __init__(self, name, subject, **kwargs): super().__init__(name=name, **kwargs) self.subject = subject class Athlete(Person): def __init__(self, name, sport, **kwargs): super().__init__(name=name, **kwargs) self.sport = sport class StudentAthlete(Student, Athlete): def __init__(self, name, subject, sport): super().__init__(name=name, subject=subject, sport=sport) ``` This pattern is more work than the earlier example, but each initializer passes the remaining keyword arguments along. A mismatch in the signatures will raise TypeError, which is much easier to fix than silently skipping half of an object's state. ## Hierarchical inheritance Hierarchical inheritance has several children sharing one parent. The parent holds the common setup, while each child adds or replaces the part that differs. ![Hierarchical inheritance diagram](https://media.geeksforgeeks.org/wp-content/uploads/20200108144949/Hierarchical-inheritance1.png) ```python class Animal: def __init__(self, name): self.name = name def show_details(self): print(f"Name: {self.name}") class Dog(Animal): def __init__(self, name, breed): super().__init__(name) self.breed = breed def show_details(self): super().show_details() print(f"Breed: {self.breed}") class Cat(Animal): def __init__(self, name, color): super().__init__(name) self.color = color def show_details(self): super().show_details() print(f"Color: {self.color}") Dog("Max", "Golden Retriever").show_details() Cat("Luna", "Black").show_details() ``` Dog and Cat both inherit the name handling, but they do not need to share their breed and color fields. This shape is often cleaner than making one large Animal class full of conditionals about which species is currently active. ## Where inheritance stops helping Inheritance is useful when the child can be passed to code that expects the parent. It is less useful when several classes merely share a few lines. In that situation, a helper function, a small mixin, or composition can keep the relationship honest. My against-interest judgment is that inheritance feels cheap at first and expensive after several layers. A subclass can depend on a parent method, an attribute name, an initializer order, and an MRO detail without making those dependencies visible at the call site. I would rather repeat a small, clear function than force unrelated classes into the same family. Use inheritance for a real type relationship, then stop the hierarchy before it becomes archaeology. ![Thank you lettering graphic](https://static.vecteezy.com/system/resources/previews/017/125/080/large_2x/thank-you-design-lettering-free-vector.jpg) ## Magic Methods in Python Explained URL: https://www.swapnoneel.site/blog/magic-methods-in-python Date: Wed, 22 Feb 2023 14:42:38 GMT Summary: Magic, or dunder, methods let Python objects respond to built-in syntax such as printing, len(), construction, and function calls. ## What magic methods do Magic methods are special methods whose names start and end with two underscores. Python calls them for ordinary operations such as creating an object, printing it, asking for its length, or calling it like a function. You may also hear them called dunder methods. You usually do not call these methods directly. You write the method that matches the behavior you want, then use normal Python syntax and let Python make the call. The useful way to learn them is to start with the syntax you want your object to support. If `print(playlist)` should be readable, look at `__str__`. If `len(playlist)` should mean something, look at `__len__`. You do not need to memorise a catalogue of every dunder method before writing a class. ## **init** sets the initial state Python calls `__init__` after it creates a new instance. Put the initial instance attributes there. ```python class Playlist: def __init__(self, songs): self.songs = list(songs) playlist = Playlist(["Intro", "Signal"]) print(playlist.songs) ``` The `list(songs)` call makes a new list for the object. The initializer prepares the object; it does not return the object itself. ## **str** and **repr** describe an object `str(value)` and `print(value)` prefer `__str__`, which should be readable to a person. `repr(value)` is meant for debugging and should contain enough detail to identify the value clearly. ```python class Playlist: def __init__(self, songs): self.songs = list(songs) def __str__(self): return ", ".join(self.songs) def __repr__(self): return f"Playlist({self.songs!r})" playlist = Playlist(["Intro", "Signal"]) print(playlist) print(repr(playlist)) ``` The readable form is `Intro, Signal`. The representation is `Playlist(['Intro', 'Signal'])`. A `__repr__` result does not have to be executable, but showing the important state makes debugging much less frustrating. ## **len** supports len() Define `__len__` when your object has a meaningful size. Python expects the method to return a non-negative integer. ```python class Playlist: def __init__(self, songs): self.songs = list(songs) def __len__(self): return len(self.songs) playlist = Playlist(["Intro", "Signal"]) print(len(playlist)) ``` The call to `len(playlist)` is Python's friendly syntax for asking the object for `playlist.__len__()`. The same idea applies to comparisons. If two objects represent the same value, `__eq__` can define what `first == second` means: ```python class Song: def __init__(self, title): self.title = title def __eq__(self, other): if not isinstance(other, Song): return NotImplemented return self.title == other.title print(Song("Signal") == Song("Signal")) ``` Without `__eq__`, two separate `Song` objects compare by identity, even when their attributes match. Returning `NotImplemented` for an unrelated type lets Python handle that comparison instead of pretending every object is comparable to a `Song`. ## **call** makes an object callable Functions are objects too. A regular object can act like a function when its class defines `__call__`. ```python class Multiplier: def __init__(self, factor): self.factor = factor def __call__(self, value): return value * self.factor double = Multiplier(2) print(double(5)) ``` `double(5)` calls `double.__call__(5)`, so the object remembers its factor between calls. This pattern is useful when a callable needs configuration or state. Another common dunder is `__iter__`, which lets an object participate in a `for` loop. For a playlist, iteration should expose the songs in the same order a caller expects: ```python class Playlist: def __init__(self, songs): self.songs = list(songs) def __iter__(self): return iter(self.songs) playlist = Playlist(["Intro", "Signal"]) for song in playlist: print(song) ``` These methods make an object fit a familiar Python protocol. That is the real benefit: callers can use normal language features instead of learning a private API for every class. My caveat is to add a magic method only when normal syntax becomes clearer. A custom `__str__` often helps immediately. A pile of clever dunders can make a class feel like it is fighting Python instead of working with it. Keep return types and failure behavior unsurprising: `__len__` should return a non-negative integer, `__str__` should return a string, and an operator should reject unsupported values clearly. ![Painted thank you label graphic](https://img.freepik.com/free-vector/painted-thank-you-label-template_23-2148689616.jpg?w=1380&t=st=1677075508~exp=1677076108~hmac=168e84f6c0a2f5c63b505e2ac25f9d6200ecf461d2fea92d9e8526809c011186) ## Overriding and Overloading in Python Explained URL: https://www.swapnoneel.site/blog/overriding-overloading-in-python Date: Tue, 21 Feb 2023 11:34:18 GMT Summary: Method overriding replaces inherited behavior, while operator overloading gives expressions such as + a meaning for your own objects. Inheritance becomes useful when the calling code can ask different objects to do the same job. A `Circle` and a `Rectangle` can both answer `area()`, even though the calculation is different. The caller keeps one method name, while each class supplies the behavior that belongs to it. There are two different ideas in this post. Method overriding changes inherited behavior. Operator overloading gives a familiar operator, such as `+`, a meaning for your own class. They are related because both depend on Python choosing a method at runtime, but they solve different problems. ## Replacing inherited behavior When a child class defines a method with the same name as a method in its parent, Python finds the child implementation first. That is method overriding. Here is a complete example with one common interface: ```python class Shape: def area(self): raise NotImplementedError("Each shape must define area()") class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): return 3.14159 * self.radius ** 2 class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height shapes = [Circle(2), Rectangle(3, 4)] for shape in shapes: print(shape.area()) ``` The output is approximately `12.56636` followed by `12`. The loop does not need an `isinstance()` check because every object follows the same `area()` contract. That is the useful part of overriding: the caller can stay ignorant of the concrete class. Python does not enforce an identical signature or return type when you override a method. Your design still should. If every shape is expected to answer `area()` without arguments, adding a required argument in one child breaks the promise made by the parent. Inheritance does not remove the need to keep an interface consistent. ## Keeping part of the parent behavior Sometimes the child needs to add a detail rather than replace everything. `super()` calls the next implementation in Python's method resolution order, which is usually the parent method in a simple hierarchy. For example, the parent can provide a general description: ```python class Shape: def describe(self): return "This object is a shape." class Circle(Shape): def describe(self): parent_description = super().describe() return f"{parent_description} It is specifically a circle." print(Circle().describe()) ``` The output contains both sentences. My caveat is that `super()` is easy to add mechanically and harder to justify mechanically. Use it when the parent's work remains part of the child behavior. If the child is meant to replace that behavior completely, calling `super()` only makes the result harder to follow. ## Giving operators a meaning Operator overloading lets a class define what an operator means for its instances. Python translates an expression such as `p1 + p2` into a special method call, roughly `p1.__add__(p2)`. These are called dunder methods because their names begin and end with two underscores. ### A point that can be added Suppose a point has an `x` coordinate and a `y` coordinate. Adding two points coordinate by coordinate is a meaning a reader can understand, so `+` is a reasonable fit: ```python class Point: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): if not isinstance(other, Point): return NotImplemented return Point(self.x + other.x, self.y + other.y) p1 = Point(1, 2) p2 = Point(3, 4) p3 = p1 + p2 print(p3.x, p3.y) ``` The output is `4 6`. Returning `NotImplemented` tells Python that this operand type is unsupported and gives the other operand a chance to handle the operation. Returning `None` would be different: the expression would appear to work and leave you with an unusable result. The same idea maps other operators to special methods: `-` calls `__sub__()`, `*` calls `__mul__()`, `<` calls `__lt__()`, and `==` calls `__eq__()`. There is one naming trap. Python does not support traditional method overloading where several methods share one name and differ only by parameter types. If you define `load()` twice, the second definition replaces the first. Default arguments or `*args` can support different call shapes, but a single clear method is usually easier to test. So the judgment is fairly simple. Override a parent method when a child needs a different implementation of the same contract. Overload an operator when the resulting expression reads naturally and rejects unsupported types clearly. If the expression needs a paragraph of explanation before it makes sense, a named method is probably the better design. ![Thank you banner graphic](https://www.incimages.com/uploaded_files/image/1920x1080/getty_469566889_105923.jpg) ## Class Methods in Python with Examples URL: https://www.swapnoneel.site/blog/class-methods-in-python Date: Mon, 20 Feb 2023 11:59:17 GMT Summary: Python class methods receive the class as cls, so they can build objects from alternate inputs and manage class-level behavior without an existing instance. ## Start with the question of who owns the work Before talking about decorators, ask one plain question: what does this method need to know? An instance method needs one particular object, so Python gives it that object as self. A class method needs the class, so Python gives it the class as cls. A static method needs neither. That is the whole split, and most of the confusing explanations become easier once you keep that ownership question in view. Here is a small class with one method that belongs to an object and another that belongs to the class: ```python class User: def __init__(self, name, role): self.name = name self.role = role def describe(self): return f"{self.name} is a {self.role}." @classmethod def guest(cls): return cls("Guest", "reader") user = User.guest() print(user.describe()) ``` describe() cannot do anything useful until a User exists, because it reads self.name and self.role. guest() has no existing user to inspect. It is a recipe for making one, so Python calls it with User as cls. The output is: ```text Guest is a reader. ``` That is why User.guest() works before you have an instance. The decorator changes how the function is bound when you access it through the class. ## What the decorator changes Without @classmethod, this method is an ordinary function sitting in the class body. If you call it through an instance, Python supplies that instance as the first argument. With @classmethod, Python stores a class-bound method instead and supplies the class. You can see the practical difference by trying to use an instance method as a factory: ```python class Ticket: def __init__(self, number): self.number = number def from_text(self, text): return Ticket(int(text)) # Ticket.from_text("42") # TypeError: self is missing ticket = Ticket(1) print(ticket.from_text("42").number) ``` The commented call fails because from_text expects self. You have to create a meaningless Ticket(1) before you can use it. The class method version expresses the intent directly: ```python class Ticket: def __init__(self, number): self.number = number @classmethod def from_text(cls, text): return cls(int(text)) ticket = Ticket.from_text("42") print(ticket.number) ``` Now parsing and construction have a name of their own, and callers do not need to know how the text is converted. ## Alternative constructors are the sweet spot The usual reason to write a class method is that one type can arrive in several input formats. Keep the normal **init** for the canonical arguments, then add named entry points for other formats. ```python class Person: def __init__(self, name, age): self.name = name self.age = age @classmethod def from_string(cls, text): name, age_text = text.split(",", maxsplit=1) return cls(name.strip(), int(age_text)) @classmethod def child(cls, name): return cls(name, 0) print(Person.from_string("John Doe, 30").age) print(Person.child("Mina").name) ``` The constructor still owns the actual object setup. The class methods only translate input into the arguments that the constructor expects. If you later add validation in **init**, both alternate paths get it automatically. There is one detail here that is easy to miss. Use cls(...), not Person(...), inside a class method: ```python class Employee(Person): pass employee = Employee.from_string("Ravi, 28") print(type(employee).__name__) ``` The output is Employee. Python passes the class used for the call as cls, so the method stays friendly to subclasses. Hard-code Person(...) and you quietly throw that behavior away. Bad input still fails. A string without a comma raises ValueError during unpacking, and text such as "Ravi, twenty" raises ValueError when int() runs. That is not a class method problem. It is the parser telling you that the input is not in the format it promised to accept. If the format comes from users or a file, catch those errors at the boundary and report the bad input there. ## Class methods versus static methods A static method is useful when a function is conceptually grouped with a class but does not need either the object or the class: ```python class Person: @staticmethod def valid_age(age): return isinstance(age, int) and age >= 0 print(Person.valid_age(30)) ``` This could be a module-level function too. Keeping it on Person is reasonable if the rule is meaningful only in that small namespace. A class method is the better choice when the class itself matters, especially for factories that must preserve subclasses. A regular method is the right choice when the answer depends on one object's state. My against-interest judgment is that class methods are easy to overuse. If a function does not construct an object or work with class-wide state, putting @classmethod on it adds ceremony and makes the reader wonder what cls is for. Start from the data the method needs. The first argument usually tells you which kind of method you actually have. ![Thank you label illustration](https://img.freepik.com/free-vector/painted-thank-you-label-template_23-2148689616.jpg?w=1380&t=st=1676893691~exp=1676894291~hmac=9f0960bb4730c2bbfdc9558840a6a8ed356377041f759a66392bdfff8f0612f2) ## Class Variables vs Instance Variables in Python URL: https://www.swapnoneel.site/blog/class-variables-vs-instance-variables Date: Sun, 19 Feb 2023 11:38:55 GMT Summary: Class variables live on the class and are shared by default, while instance variables belong to one object. Learn how lookup and mutable defaults affect Python classes. ## The lookup happens in two places Take account.owner. Python first asks the account object whether it has an owner attribute. If it does not, Python checks the class and then the classes above it. That small lookup rule is why a class attribute can appear to belong to every instance. The value has not been copied into each object, though. An instance variable is stored on one object. A class variable is stored on the class and is shared by every object that reads it. Once you see where the value lives, the rest follows. ## Instance variables describe one object Put per-object state on self, usually in **init**. Each call to a class creates a separate object, and each object gets its own attribute dictionary. ```python class User: def __init__(self, name, points): self.name = name self.points = points def summary(self): return f"{self.name}: {self.points} points" first = User("John", 12) second = User("Jane", 7) first.points += 5 print(first.summary()) print(second.summary()) ``` The output is: ```text John: 17 points Jane: 7 points ``` first.points += 5 changes the attribute on first. It has no route to second.points. Both objects share the summary method through the class, but their data is separate. You can inspect that storage directly while learning: ```python print(first.__dict__) print(second.__dict__) ``` Each dictionary contains its own name and points. This is a useful debugging trick, but do not build a design around **dict**; some Python objects use **slots** and do not have one. ## Class variables describe the class Put shared data in the class body. A count is a good example because there should be one count for all User objects. ```python class User: total_users = 0 def __init__(self, name): self.name = name type(self).total_users += 1 first = User("John") second = User("Jane") print(User.total_users) print(first.total_users) print(second.total_users) ``` All three prints show 2. The first lookup finds total_users on User. The other two lookups fail to find it on the individual objects, then find it on the class. type(self).total_users is a deliberate choice. It lets a subclass keep its own count instead of always updating the User count. If you want one global count for the whole family, write User.total_users += 1 and make that ownership explicit. Also, prefer User.total_users when reading class-owned data. first.total_users is legal, but it hides the fact that the value is shared. ## Assignment can hide the class value This is the part that catches people: ```python class User: role = "reader" first = User() second = User() first.role = "admin" print(first.role) print(second.role) print(User.role) ``` The output is: ```text admin reader reader ``` The assignment did not update User.role. It created a new role attribute on first, so that one object now shadows the class value. Delete first.role and lookup falls back to User.role again: ```python del first.role print(first.role) ``` This is why class attributes are fine for defaults that instances may override, but they are a poor substitute for a shared setting that callers can casually shadow. ## Mutable class variables share one object Numbers make sharing look harmless. Lists expose the trap immediately: ```python class Team: members = [] def add_member(self, name): self.members.append(name) red = Team() blue = Team() red.add_member("Asha") print(red.members) print(blue.members) ``` Both lines print ['Asha'] because red.members and blue.members found the same list on Team, and append() mutated that list in place. If each team needs its own collection, create it in **init**: ```python class Team: def __init__(self): self.members = [] def add_member(self, name): self.members.append(name) ``` The class-level list is not always wrong. A shared immutable default, or a deliberately shared registry, can be exactly what you need. The failure comes from forgetting that a mutable value is one shared object until you create separate copies. ## My default choice For ordinary application state, I choose instance variables first. They make ownership obvious, prevent accidental cross-object changes, and fit the way most objects are used. I reach for class variables when the value genuinely belongs to the class: a constant, a shared configuration default, or a counter that the class owns. So the practical rule is simple: put per-object data on self, and make shared data visibly class-owned. Be extra suspicious of class-level lists and dictionaries. But that's just me, and your workflow might be different. ![Thank you graphic for class vs instance variables blog](https://img.freepik.com/free-vector/painted-thank-you-label-template_23-2148689616.jpg?w=1380&t=st=1676893691~exp=1676894291~hmac=9f0960bb4730c2bbfdc9558840a6a8ed356377041f759a66392bdfff8f0612f2) ## Getters and Setters in Python Explained URL: https://www.swapnoneel.site/blog/getters-and-setters-in-python Date: Sat, 18 Feb 2023 13:32:09 GMT Summary: Python property() lets a class validate or compute an attribute without making callers use explicit getter and setter methods. ## Keep the public name simple Suppose a Person object stores an age. You want callers to write person.age, not person.get_age() every time they read it. At first, a plain attribute is enough. The trouble begins when a caller assigns -4 or the string "old", and your class has no place to reject it. A property keeps the pleasant attribute syntax while running Python code on reads and writes. It is a useful boundary, but it is not a magic private field. Python still trusts you to respect the interface you chose. ## A property starts as a getter The property decorator turns a method into an attribute-like read: ```python class Person: def __init__(self, age): self._age = age @property def age(self): return self._age person = Person(30) print(person.age) ``` The output is 30. Python sees person.age, calls the age method, and returns the value from \_age. The leading underscore is a convention that says the storage is for internal use. It does not stop someone from writing person.\_age = -4. Because the property has no setter yet, this works: ```python print(person.age) ``` But person.age = 31 raises AttributeError. A read-only property is useful for a value that the object calculates or exposes without allowing outside code to replace it. ## Add validation with a setter Decorate a second method with age.setter. The method receives the value on the right side of the assignment: ```python class Person: def __init__(self, age): self.age = age @property def age(self): return self._age @age.setter def age(self, value): if not isinstance(value, int): raise TypeError("age must be an integer") if value < 0: raise ValueError("age cannot be negative") self._age = value person = Person(30) person.age = 31 print(person.age) ``` The initializer assigns to self.age instead of self.\_age on purpose. That sends the first value through the same validation as every later assignment. Person(-1) raises ValueError, and Person("thirty") raises TypeError before an invalid object can escape. The setter does not have to store the value unchanged. It can normalize it, convert units, or reject a value that violates the class's rules: ```python class Temperature: def __init__(self, celsius): self.celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("temperature is below absolute zero") self._celsius = float(value) @property def fahrenheit(self): return self.celsius * 9 / 5 + 32 temperature = Temperature(20) print(temperature.fahrenheit) ``` fahrenheit is computed from celsius, so there is no second piece of state to keep in sync. If you stored both values, every update would create another chance for them to disagree. ## The recursion trap Inside a property getter, return the backing attribute, not the property itself. Inside the setter, assign to \_age, not age: ```python class BrokenPerson: @property def age(self): return self.age @age.setter def age(self, value): self.age = value ``` Reading age calls the getter, which reads age again, which calls the getter again until Python raises RecursionError. Assigning to age creates the same loop in the setter. The private-looking backing name prevents that accidental self-call. ## Do you need a property at all Usually, no. A plain public attribute is a good starting point when it has no validation or calculation behind it. You can replace that attribute with a property later without changing code that reads person.age or assigns to it. That is one of the nicest parts of the pattern. Do not create a getter and setter that only return and assign the same value for the sake of ceremony. In Python, properties earn their place when a read needs a calculation, a write needs a rule, or the stored representation should stay separate from the public name. My caveat is to keep the public name stable and the property small. If a getter performs a database query or a setter triggers half the application, attribute syntax can hide a surprisingly expensive operation. For simple validation and computed values, property is a good fit. For larger actions, an explicit method tells the reader more honestly what a call will do. ![Thank you banner graphic](https://etiquettejulie.com/wp-content/uploads/2017/01/thank-you-from-christian-vision-alliance.jpg) ## Constructors and Decorators in Python with Examples URL: https://www.swapnoneel.site/blog/constructors-and-decorators-in-python Date: Fri, 17 Feb 2023 15:16:04 GMT Summary: Python creates an object before __init__ initializes it, while decorators wrap functions to add behavior. This guide shows both ideas with small examples. ## A class call has two stages When you write Details("Crab", "Crustaceans"), Python does more than run a normal function. It creates an object, then initializes that object with the arguments you passed. The method that creates the object is **new**. The method that prepares its attributes is **init**. People often call **init** the constructor, and that shorthand is fine for everyday work, but the distinction matters when you need to control object creation itself. For most classes, you only write **init**: ```python class Details: def __init__(self, animal, group): self.animal = animal self.group = group details = Details("Crab", "Crustaceans") print(details.animal, "belongs to the", details.group, "group.") ``` self is the newly created object. The assignments attach animal and group to that object, so another Details instance can store different values. The output is: ```text Crab belongs to the Crustaceans group. ``` If you leave out **init**, Python can still create instances when no setup is required. Add an initializer when the object needs a known starting state: ```python class Counter: def __init__(self): self.value = 0 def increment(self): self.value += 1 counter = Counter() counter.increment() print(counter.value) ``` The output is 1. A common mistake is returning a value from **init**. It must return None; its job is to configure self, not replace it. return self and return 5 both raise TypeError when Python calls the class. ## When new matters You can define **new** when object creation needs special rules, such as returning an existing object or creating an immutable value. That is advanced territory. If all you need is to copy arguments into attributes, **init** is the right place. This version makes the order visible: ```python class Example: def __new__(cls, value): print("creating") return super().__new__(cls) def __init__(self, value): print("initializing") self.value = value example = Example(10) print(example.value) ``` The lines print creating, initializing, and 10, in that order. If **new** returns an object that is not an instance of cls, Python will not continue with the usual **init** call. That is one reason not to override it casually. ## A decorator replaces the name with a callable result Now switch from objects to functions. A decorator is a callable that receives a function and returns something that will be used in its place. The @ syntax is just a readable spelling of that assignment. ```python def show_call(func): def wrapper(): print("before") result = func() print("after") return result return wrapper @show_call def greet(): print("hello") return 42 print(greet()) ``` Python reads the decoration roughly like this: ```python def greet(): print("hello") return 42 greet = show_call(greet) ``` After that assignment, the name greet refers to wrapper. Calling greet() prints before, then hello, then after, and finally 42. The original function still runs because the wrapper calls func(). The final return result is easy to forget. If you remove it, the log still appears, but print(greet()) prints None. That kind of bug feels strange when the function body clearly returns a value, because the wrapper has quietly swallowed it. ## A decorator that accepts real arguments A wrapper with no parameters only works for a function with no arguments. In normal code, use \*args and \*\*kwargs so the wrapper can pass along positional and keyword arguments. ```python from functools import wraps def log_function_call(func): @wraps(func) def wrapper(*args, **kwargs): print(f"calling {func.__name__}") result = func(*args, **kwargs) print(f"{func.__name__} returned {result}") return result return wrapper @log_function_call def add(first, second): return first + second print(add(2, second=3)) ``` wraps is worth keeping. Without it, add.**name** would be wrapper, and tools that inspect the function would see the wrapper's metadata instead of add's. This is not just cosmetic when a framework uses names, signatures, or docstrings. Exceptions travel through the wrapper too. If add("2", 3) runs, the addition raises TypeError, the second log line is skipped, and the error reaches the caller. Add a try and finally only when you have a real reason to log failures or clean up resources. ## Where each tool earns its place Use **init** for ordinary object setup. Use **new** only when the act of creating the object needs custom behavior. Use a decorator when the same surrounding behavior belongs around several functions, such as logging or permission checks. My caveat is that decorators hide a call. A tiny @ line can change arguments, errors, metadata, and return values while leaving the function body untouched. When a decorated function behaves oddly, inspect the decorator before blaming the function. I like decorators, but I trust them only when the wrapper is short enough to read in one sitting. ![Thank you card maker graphic](https://cdn.pizap.com/pizapfiles/images/thank_you_card_maker_app01.jpg) ## Introduction to OOPs in Python URL: https://www.swapnoneel.site/blog/introduction-to-oops Date: Thu, 16 Feb 2023 17:58:21 GMT Summary: Object-oriented programming groups data and behavior into classes and objects. This Python guide explains abstraction, encapsulation, inheritance, and polymorphism without treating OOP as a rule. ## What object-oriented programming tries to solve Procedural code starts with actions: read a value, transform it, then save or print the result. That style is perfectly fine for a script that imports a file once. As the program grows, though, the data and the functions allowed to change it can end up scattered across many modules. Object-oriented programming, or OOP, gives related state and behavior a home. A class describes a kind of object. An object is one concrete value made from that class. You are not required to use classes for every problem, and forcing a tiny script into a hierarchy is a quick way to make simple code feel strange. Start with a class that keeps a person's data beside an operation that uses it: ```python class Person: def __init__(self, name): self.name = name def greet(self): return f"Hello, I am {self.name}." first = Person("Ryan") second = Person("Mina") print(first.greet()) print(second.greet()) ``` Person is the class. first and second are objects, also called instances. The class supplies the greet method, while each object stores its own name. The output is: ```text Hello, I am Ryan. Hello, I am Mina. ``` That relationship is the foundation. The four words usually connected with OOP describe different ways of using it. ## Abstraction hides the steps you do not need Abstraction means exposing an operation while keeping its internal steps out of the caller's way. When you call first.greet(), you do not need to rebuild the string or know where name is stored. The method gives you the operation you need. The same idea exists in ordinary functions. A function that loads and validates a configuration file is an abstraction even if the program contains no class. The [OOP concepts overview](https://stackify.com/oops-concepts-in-java/) describes this as hiding complexity behind a simpler interface. Good abstraction has a limit. If the method name is vague, or if it secretly opens files, makes network requests, and changes global state, the caller has a harder time predicting what will happen. Hide the steps that are implementation details, but keep the public action honest. ## Encapsulation keeps rules near the data Encapsulation means putting state and the operations that protect it in the same place. Python does not enforce private fields in the same way as some languages. A leading underscore is a convention, not a locked door, but it tells readers which attribute the class owns internally. ```python class Wallet: def __init__(self, amount=0): if amount < 0: raise ValueError("amount cannot be negative") self._balance = amount def deposit(self, amount): if amount <= 0: raise ValueError("deposit must be positive") self._balance += amount def spend(self, amount): if amount <= 0 or amount > self._balance: raise ValueError("invalid spending amount") self._balance -= amount @property def balance(self): return self._balance wallet = Wallet(20) wallet.deposit(5) wallet.spend(8) print(wallet.balance) ``` The output is 17. The class owns the rules for changing the balance, so callers do not have to remember every check. Someone can still write wallet.\_balance = -100, because Python trusts convention, but ordinary code has a clear public path. This is where encapsulation pays off. If the storage changes from a number to another representation later, the deposit, spend, and balance interface can stay the same. ## Inheritance describes a narrower type Inheritance lets a new class reuse or replace behavior from an existing class: ```python class Animal: def __init__(self, name): self.name = name def speak(self): return "Some sound" class Dog(Animal): def speak(self): return "Bark" dog = Dog("Max") print(dog.name) print(dog.speak()) ``` Dog gets the name setup from Animal and supplies a more specific speak method. This is a reasonable relationship because a dog is an animal. A Dog has a Collar, though, so a collar would usually be stored as another object rather than added as a parent class. Inheritance is useful when code genuinely expects the parent type. It becomes awkward when the only shared feature is a few lines of implementation. A helper function or composition can be clearer in that case. ## Polymorphism lets the caller ignore the concrete type Polymorphism means different objects can respond to the same operation in their own way. Python often handles this through duck typing: the function asks for behavior instead of checking a long list of class names. ```python class Dog: def speak(self): return "Bark" class Cat: def speak(self): return "Meow" def announce(animal): print(animal.speak()) announce(Dog()) announce(Cat()) ``` announce does not need separate branches for Dog and Cat. It only needs an object with a speak method. Pass an object without speak and Python raises AttributeError at the call, which is a useful, direct failure. If you need a friendlier error, validate the interface before doing work, but do not add type checks just to make the code look formal. Python's built-in types use the same idea all over the place. Anything with a **len** method can work with len(), and anything iterable can work in a for loop. The class name matters less than the behavior the operation requires. ## OOP is a tool, not a rule Classes help when state and behavior belong together, when several objects share a clear interface, or when a type has rules that should live in one place. A plain function and a dictionary can be better for a one-off transformation. My honest view is that OOP is easiest to understand after you stop treating its four labels as a checklist. Start with the data and the operations. If they naturally belong together, make a class. If a class would only wrap one function and a couple of values, skip it. The design should make the next change easier, not earn points for containing more objects. ![Thank you image](https://iag.me/assets/thank-you.jpg.webp) ## Enumerate Function in Python with Examples URL: https://www.swapnoneel.site/blog/enumerate-function-in-python Date: 2023-02-14T08:12:35.719Z Summary: enumerate() gives you each item with its index, so loops stay readable without a manual counter. Use it with lists, tuples, strings, and custom starting points. Have you ever written `index = 0` above a loop, incremented it at the bottom, and then wondered what happens when the loop gets a `continue`? That counter works, but it gives you another piece of state to maintain. `enumerate()` puts the position beside the item for you. It accepts an iterable and returns an iterator that produces pairs such as `(0, "apple")`. Python creates each pair as the loop requests it, so you can use it without building a second list first. ## The loop you actually want The built-in function works with lists, tuples, strings, and other iterable objects: ```python # Loop over a list and print the index and value of each element fruits = ['apple', 'banana', 'mango'] for index, fruit in enumerate(fruits): print(index, fruit) ``` The loop unpacks each pair into `index` and `fruit`. The output is: ```text 0 apple 1 banana 2 mango ``` If you want to inspect every pair at once, convert the iterator to a list: ```python fruits = ['apple', 'banana', 'mango'] print(list(enumerate(fruits))) ``` That prints `[(0, 'apple'), (1, 'banana'), (2, 'mango')]`. In ordinary loops, do not add `list()` just to make the code look familiar. The loop already consumes the iterator one pair at a time, which avoids storing another collection. The same pattern is handy when you need to replace or inspect one item. For example, this prints a numbered warning only for a missing value: ```python statuses = ["ok", "missing", "ok"] for position, status in enumerate(statuses, start=1): if status == "missing": print(f"Row {position} needs attention") ``` The output is `Row 2 needs attention`. Notice that the list remains unchanged. `enumerate()` reports positions; it does not edit the iterable for you. This is usually clearer than indexing with `range(len(fruits))`: ```python for index in range(len(fruits)): print(index, fruits[index]) ``` The indexed version is not always wrong. It makes sense when you need to compare neighboring positions, assign back into a mutable list, or use the same index for several sequences. If you only need the value and its position, `enumerate()` says that directly and gives you fewer moving parts. ## Starting from one instead of zero Python uses zero-based indexes by default, so the first item gets index `0`. That is useful when the number is an index into another sequence. If you are showing positions to a person, starting at `1` usually reads better. Pass the starting value with `start`: ```python # Loop over a list and print the index (starting at 1) and value of each element fruits = ['apple', 'banana', 'mango'] for index, fruit in enumerate(fruits, start=1): print(index, fruit) ``` Now the output is: ```text 1 apple 2 banana 3 mango ``` You can format the position in the loop without changing the original list: ```python fruits = ['apple', 'banana', 'mango'] for index, fruit in enumerate(fruits): print(f'{index+1}: {fruit}') ``` The output is: ```text 1: apple 2: banana 3: mango ``` ## Lists are not special A tuple works the same way: ```python # Loop over a tuple and print the index and value of each element colors = ('red', 'green', 'blue') for index, color in enumerate(colors): print(index, color) ``` Strings are iterable too, so `enumerate()` can give you each character and its position: ```python # Loop over a string and print the index and value of each character s = 'hello' for index, c in enumerate(s): print(index, c) ``` You can pass a generator as well. That is where the lazy behavior becomes more useful: ```python def read_numbers(): for number in range(3): yield number * 10 for index, number in enumerate(read_numbers(), start=1): print(index, number) ``` The loop prints `1 0`, `2 10`, and `3 20` without asking the generator for every value in advance. You can enumerate dictionary items too. `enumerate()` supplies the position, while `.items()` supplies the key and value: ```python prices = {"tea": 20, "coffee": 30} for position, (name, price) in enumerate(prices.items(), start=1): print(position, name, price) ``` The nested unpacking may look busy the first time you see it, but the names tell you what each value means. A separate counter would add no useful information here. There are two easy mistakes. First, `start=1` changes the number reported by `enumerate()`, not the indexes stored in your list. Second, the position is not a permanent ID. If you remove items while iterating, later positions describe the current pass through the data. Changing a collection during a loop can create its own problems, so build a new collection when you need to filter or reorder values. For a normal read-only loop, `enumerate()` is the cleanest answer whenever you need an item and its position. It is small, readable, and removes the counter bug before you have to debug it. ![Handwritten thank you typography](https://img.freepik.com/free-vector/painted-thank-you-label-template_23-2148689616.jpg?w=1380&t=st=1676893691~exp=1676894291~hmac=9f0960bb4730c2bbfdc9558840a6a8ed356377041f759a66392bdfff8f0612f2) ## OS Modules in Python with Examples URL: https://www.swapnoneel.site/blog/os-modules-in-python Date: 2023-02-12T13:58:39.078Z Summary: The Python os module connects your code to the operating system. Read and write files, inspect folders, create directories, and run shell commands with care. Python usually lets you forget that a program is running on top of an operating system. You call `open()`, read a string, and carry on. The `os` module is where that boundary becomes visible: folders have names, files have descriptors, processes have exit codes, and each operating system has its own path rules. You do not need to memorize the whole module. Think in three buckets. Are you working with a path, with a file at a lower level, or with a command that another process should run? The answer points you toward a different part of `os`. ## Opening files at the lower level Most Python code should use the built-in `open()` function because it gives you a convenient file object and closes it neatly with `with`. `os.open()` is lower level. It returns an integer file descriptor, and you are responsible for reading bytes and closing that descriptor. This complete example creates a small file, reads it back, and removes it at the end. Run it in a scratch directory if you want to watch the file appear: ```python import os path = "os-module-demo.txt" try: fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644) try: os.write(fd, b"Hello from a file descriptor!") finally: os.close(fd) fd = os.open(path, os.O_RDONLY) try: contents = os.read(fd, 1024) print(contents.decode("utf-8")) finally: os.close(fd) finally: if os.path.exists(path): os.remove(path) ``` The printed text is `Hello from a file descriptor!`. The flags explain the first open call: `O_WRONLY` requests writing, `O_CREAT` creates the file if it is missing, and `O_TRUNC` clears old contents. The numeric mode controls permissions on systems that use them. The `try` and `finally` blocks matter because leaving a descriptor open can exhaust the process's file handles. For normal text files, this is more work than you need. Use `os.open()` when an API requires a descriptor or when you genuinely need low-level flags. The built-in file object is the better default. ```python import os # The high-level version closes the file for you with open("message.txt", "w", encoding="utf-8") as file: file.write("Hello, world!") ``` ## Inspecting paths and folders `os.listdir()` returns the names inside a directory as strings. The result is not sorted, so sort it when the order is part of what a person will read: ```python import os files = sorted(os.listdir(".")) print(files) ``` `os.mkdir()` creates one directory and raises an error if the directory already exists. `os.makedirs()` is more useful when the path may contain missing parents: ```python import os os.makedirs("reports/2023", exist_ok=True) ``` The `exist_ok=True` argument makes a second run harmless. For paths that combine several parts, use `os.path.join()` instead of typing `/` or `\\` yourself: ```python import os config_path = os.path.join("config", "settings.json") print(config_path) ``` The printed separator depends on the operating system. That is why joining path parts is safer than assembling one string by hand. You can also inspect a value without guessing what it means: ```python import os print(os.path.abspath("reports")) print(os.path.exists("reports")) ``` The first line gives the full path, while the second prints `True` if the directory exists. These checks still have a race condition if another process changes the filesystem immediately afterward, so handle the actual operation's exception as well when the code matters. ## Asking the shell to do something `os.system()` sends a string to the system shell and returns an exit status. It does not give your Python code the command's printed text: ```python import os status = os.system("echo Hello from the shell") print(f"exit status: {status}") ``` The shell prints `Hello from the shell`, then Python prints a status. A zero status generally means the command completed successfully, but the exact value can be represented differently across platforms. `os.popen()` gives you a file-like object for command output: ```python import os with os.popen("echo Hello from the shell") as output_file: output = output_file.read() print(output.strip()) ``` Do not build a shell command by joining untrusted user input into a string. Shell metacharacters can change what actually runs. For new code that needs arguments, error handling, or separate output streams, use `subprocess.run()` with a list of arguments instead. The `os` shortcuts are useful for learning the boundary, but they are easy to outgrow. ## The practical rule Start with Python's high-level file and path tools. Reach for `os.path`, `os.listdir()`, or `os.makedirs()` when you need to inspect the machine. Use `os.open()` and shell calls only when their lower-level behavior is the reason you are writing the code. That boundary keeps the module useful without turning every file operation into a permissions and cleanup puzzle. ![Thank you placard concept illustration](https://img.freepik.com/free-vector/thank-you-placard-concept-illustration_114360-13436.jpg?w=1380&t=st=1675784022~exp=1675784622~hmac=b4748b9ac8dd94ff98a8232e0a56aa06102f42d9595f55a3b7cdc17121e72ea8) ## map(), filter(), and reduce() in Python Explained URL: https://www.swapnoneel.site/blog/the-3-most-powerful-functions-in-python Date: 2023-02-10T12:46:31.060Z Summary: map(), filter(), and reduce() each pass a function over data in a different way. Learn what they return, when they help, and when a loop is clearer. The title calls these functions powerful, but the useful part is much less dramatic. `map()`, `filter()`, and `reduce()` each describe one shape of work over a collection. If you can name the shape, you can decide whether one of them makes the code clearer or whether a normal loop is the better answer. All three accept a function as an argument. A function that receives another function is called a higher-order function, which sounds academic until you see the three questions they answer: - Should every item become a new value? - Should some items be kept and the rest discarded? - Should many values become one result? ## Transforming every item with map `map(function, iterable)` applies the function to each item. It returns a lazy iterator, so wrap it in `list()` when you need all results at once: ```text map(function, iterable) ``` ```python numbers = [1, 2, 3, 4, 5] doubled = map(lambda x: x * 2, numbers) print(list(doubled)) ``` The lambda receives one number at a time. `map()` passes each number through the multiplication, and `list()` consumes the iterator to produce `[2, 4, 6, 8, 10]`. If the transformation already exists as a function, pass that function directly: ```python names = ["asha", "mina", "rohan"] upper_names = map(str.upper, names) for name in upper_names: print(name) ``` There is no need to write `lambda name: name.upper()` here. The direct function keeps the operation visible, and the loop consumes the lazy iterator one item at a time. The iterator is single-use: ```python numbers = [1, 2, 3] doubled = map(lambda number: number * 2, numbers) print(list(doubled)) print(list(doubled)) ``` The second print is `[]` because the iterator has already been consumed. That surprises people when they store a `map()` result and expect it to behave like a list. For a simple transformation, I usually prefer a list comprehension because the result type and rule are obvious at a glance: ```python doubled = [number * 2 for number in numbers] ``` ## Keeping matching items with filter `filter(predicate, iterable)` keeps an item when the predicate returns a truthy value. A predicate is simply a function used to answer a yes-or-no question: ```text filter(predicate, iterable) ``` ```python numbers = [1, 2, 3, 4, 5] evens = filter(lambda x: x % 2 == 0, numbers) print(list(evens)) ``` The predicate returns `True` for even values, so the result is `[2, 4]`. A comprehension expresses the same rule directly and is often easier to debug: ```python evens = [number for number in numbers if number % 2 == 0] ``` Give a predicate a name when the condition carries business meaning: ```python def is_available(product): return product["stock"] > 0 and not product["archived"] products = [ {"name": "Notebook", "stock": 3, "archived": False}, {"name": "Pen", "stock": 0, "archived": False}, ] available = list(filter(is_available, products)) ``` The named function gives you a place to test the rule independently. A lambda is fine for `number % 2 == 0`; it becomes a distraction when the condition needs another explanation. ## Combining values with reduce `reduce()` repeatedly combines two values until one result remains. Unlike `map()` and `filter()`, it is not a built-in name, so import it from `functools`: ```text reduce(function, iterable) ``` ```python from functools import reduce numbers = [1, 2, 3, 4, 5] total = reduce(lambda left, right: left + right, numbers) print(total) ``` The first call combines `1` and `2`, producing `3`. The next call combines that result with `3`, then continues until the total is `15`. This is a left-to-right chain, not a mysterious shortcut. For addition, `sum(numbers)` is clearer. `reduce()` also needs a decision for an empty iterable. Without an initial value, `reduce()` raises `TypeError` when there is nothing to combine: ```python from functools import reduce print(reduce(lambda left, right: left + right, [], 0)) ``` The final `0` is the initial value, so this version prints `0`. You can also combine values into something other than a number, but check whether a normal operation says the same thing more clearly. For example, a sentence is better built with `" ".join(words)` than with a reduction that keeps adding strings. `reduce()` earns its place when the repeated combination is the useful idea, not merely because it can express the answer. The three functions can form a pipeline when each step has a separate job: ```python prices = [5, 12, 20, 3] eligible = filter(lambda price: price >= 5, prices) with_tax = map(lambda price: price * 1.18, eligible) total = sum(with_tax) print(total) ``` This works because `filter()` and `map()` stay lazy until `sum()` consumes them. If the callbacks start needing several lines or side effects, stop and write a loop. A little repetition is easier to inspect than a pipeline that hides the state changes. My caveat is that nested reductions can make a small calculation harder to debug than a normal loop. `map()` and `filter()` are fine when their iterator behavior is clear. For many everyday transformations, comprehensions read better. Use `reduce()` when the repeated combination is genuinely the point, then name the operation clearly. The judgment is straightforward: use `map()` to change every value, `filter()` to select values, and `reduce()` to collapse values into one result. But short syntax is not automatically readable syntax. If a loop explains the rule faster, write the loop. ![Thank you banner image](https://www.incimages.com/uploaded_files/image/1920x1080/getty_469566889_105923.jpg) ## Object Introspection in Python Explained URL: https://www.swapnoneel.site/blog/object-introspection-in-python Date: 2023-02-07T15:48:04.393Z Summary: Python's dir(), __dict__, id(), and help() let you inspect objects while code runs. Use them to understand attributes, identity, and documentation during debugging. ## What introspection means Introspection means asking an object about itself while the program is running. It is useful when you are learning an unfamiliar class, checking why an attribute lookup behaves strangely, or debugging a value that is not what you expected. Python gives you several built-ins for this. They answer different questions, so do not treat their output as a complete description of an object. The questions are worth separating. `type()` tells you what kind of value you have. `dir()` tells you which names might be available. `getattr()` reads a name when you only know it at runtime. `help()` points you toward the documented interface. These tools are most useful when you use the smallest one that answers the question in front of you. ## The dir() function lists names `dir(value)` returns a sorted list of names that Python considers useful for that object. The list can include methods, attributes, and dunder names inherited from a class. It is a discovery tool, not a guarantee that every name can be called successfully. ```python my_list = [1, 2, 3] names = [name for name in dir(my_list) if name in {"append", "pop"}] print(names) ``` The output is: ```text ['append', 'pop'] ``` Use `dir()` when you know roughly what you are searching for. Reading the entire list for a large object can be noisy, so filtering it often makes the result easier to use. The presence of a name does not prove that it is safe to call. A property may run code when read, and a method may need arguments. Treat `dir()` as a menu of possibilities, then inspect the signature or documentation before using an unfamiliar entry. ## The **dict** attribute shows stored attributes Many Python objects keep their instance attributes in a dictionary called `__dict__`. It is an attribute, not a function, so write `person.__dict__`, not `person.__dict__()`. ```python class Person: def __init__(self, name, age): self.name = name self.age = age person = Person("John", 30) print(person.__dict__) ``` The output is: ```text {'name': 'John', 'age': 30} ``` `__dict__` shows attributes stored directly on this instance. A class can also have a `__dict__`, and some objects, including classes that use `__slots__`, do not expose an instance dictionary. If the attribute is missing, that does not mean the object has no state. When the attribute name comes from a configuration value, use `getattr()` instead of constructing an expression: ```python class Settings: timeout = 30 settings = Settings() name = "timeout" print(getattr(settings, name, 10)) ``` The third argument is a default for a missing attribute. `hasattr()` can answer whether a lookup succeeds, but remember that it may execute a property and can hide an exception raised during that lookup. For code you control, a direct access with a clear `AttributeError` is often easier to debug. ## The id() function identifies an object during its lifetime `id(value)` returns an integer that stays the same for that object while it exists. Two live objects cannot have the same identity value. The exact number is implementation-dependent, so it may change between runs. Use the `is` operator when you want to test identity: ```python first = [] second = first third = [] print(first is second) print(first is third) print(id(first) == id(second)) ``` The output is: ```text True False True ``` Do not use `id()` to compare equal values. Two separate lists can contain the same items while representing different objects. Use `==` for value comparison and `is` for identity comparison. ## The help() function reads documentation `help(value)` opens Python's built-in documentation for a value. It can show a module, class, function, method, or object. In an interactive shell, Python may open the result in a pager, so press `q` when you are done reading. ```python help(str.upper) ``` The result includes the method's description and signature when that information is available. `help()` is especially handy when you remember a method name but not its arguments. `callable(value)` is another small check that helps during exploration. It tells you whether Python can call the value, while `isinstance(value, SomeType)` lets you check whether it fits a type or protocol you explicitly care about. Avoid turning a debugging tool into a maze of type checks; in ordinary Python code, trying the operation and handling a meaningful error can be clearer. ## A practical debugging routine When an unfamiliar object appears in your code, start with `type(value)` to identify its class. Filter `dir(value)` to find possible names, inspect `value.__dict__` when the object provides one, and use `help()` for the documentation. Use `getattr()` when the name is dynamic and `id()` only when the question is whether two names point to the same object. My caveat is that introspection shows what Python exposes, not what the author intended. Treat the output as a clue, then read the class or docstring before changing code based on a guess. Printing an object's attributes can help you find a problem, but it is not a replacement for a documented interface or a test that explains the expected behavior. ![Thank you placard concept illustration](https://img.freepik.com/free-vector/thank-you-placard-concept-illustration_114360-13436.jpg?w=1380&t=st=1675784022~exp=1675784622~hmac=b4748b9ac8dd94ff98a8232e0a56aa06102f42d9595f55a3b7cdc17121e72ea8) ## Virtual Environments in Python Explained URL: https://www.swapnoneel.site/blog/virtual-environments Date: 2023-01-27T16:11:48.129Z Summary: Python virtual environments keep each project's interpreter and packages separate. Create, activate, deactivate, and record one with requirements.txt. Your Python installation is shared by default. That feels convenient until one project needs an older package and another project needs a newer one. Install both globally and you have made the interpreter responsible for an argument it cannot resolve cleanly. A virtual environment is a project-specific directory containing an interpreter and its installed packages. It lets each project choose its own dependencies without changing every other project on the machine. The environment is not a container and it does not install a second operating system. It is a boundary around Python packages. That boundary does not make the source code portable by itself. You still need to record the packages and the Python version your project expects. The environment is disposable; the dependency description is the part you keep. ## Creating the environment Python includes the `venv` module. Run this command from the project directory. It creates a directory named `myenv`: ```bash python -m venv myenv ``` Activate it with the command for your shell: ```bash # Linux or macOS source myenv/bin/activate # Windows PowerShell .\myenv\Scripts\Activate.ps1 # Windows Command Prompt myenv\Scripts\activate.bat ``` After activation, your shell usually shows `(myenv)` in the prompt. More useful than the prompt is checking which interpreter is running: ```bash python -c "import sys; print(sys.executable)" ``` The printed path should point inside `myenv`. Commands such as `python` and `pip` now use the environment, so an install stays with this project. I prefer `python -m pip` because it makes the connection between the Python interpreter and its package installer explicit: ```bash python -m pip install requests ``` If PowerShell blocks the activation script with an execution-policy error, that is a shell policy problem, not a broken Python environment. You can still run the environment's interpreter directly, or adjust the policy according to your machine's rules. Do not copy a policy command from a random post without understanding whether it changes the policy for only your user or for the whole machine. ## Leaving the environment When you finish working, leave the environment with: ```bash # Deactivate the virtual environment deactivate ``` The command only changes the current shell. It does not delete the environment or uninstall its packages. If you close the terminal, the environment directory remains on disk. ## Recording the dependencies Do not commit the `myenv` directory to your repository. It contains machine-specific paths and can become large. Record the packages your project needs in `requirements.txt` instead: ```bash python -m pip freeze > requirements.txt ``` On another machine, create and activate a fresh environment, then install those recorded versions: ```bash python -m pip install -r requirements.txt ``` One honest caveat: `pip freeze` records everything installed in the environment, including packages you may have added while experimenting. For a small project that is often fine. For a long-lived project, review the file before committing it. A clean environment makes that review much easier. Add the environment directory to `.gitignore` as well: ```text myenv/ .venv/ venv/ ``` The exact directory name is your choice. `.venv` is common too; consistency matters more than the name. For a small tutorial, `requirements.txt` is enough. A package or a larger application may eventually use `pyproject.toml` to describe dependencies and build settings. The file format can change, but the workflow does not: a new machine should be able to create a clean environment from a short, reviewable declaration. If the environment gets confused, recreate it instead of trying to repair every installed package by hand. First update the dependency file, remove the disposable environment directory, and run `python -m venv` again. The source code is outside that directory, so rebuilding it is normally the safer fix. ## The routine that keeps it useful Create the environment once, activate it whenever you work on the project, install packages through `python -m pip`, and deactivate it when you are done. When a teammate checks out the project, they can recreate the environment from `requirements.txt` instead of receiving a copy of yours. The routine is not glamorous, and it does not solve every deployment problem. It does solve the common mistake of installing a package into one interpreter and running the program with another. For Python projects, that is enough reason to make virtual environments your default. ![Thank you banner image](https://www.incimages.com/uploaded_files/image/1920x1080/getty_469566889_105923.jpg) ## Sets in Python with Examples URL: https://www.swapnoneel.site/blog/sets-in-python Date: 2023-01-25T18:09:15.443Z Summary: Python sets store unique hashable values and make membership, union, intersection, difference, and symmetric difference easy. You have a list of email addresses and want to know who has already signed up. You can scan the list every time, or you can put the addresses in a set and ask one direct question: `email in subscribers`. A set is a mutable collection of unique, hashable values. It is built for membership checks and operations between groups, not for keeping items at numbered positions. That one distinction explains most of the behavior that surprises people at first. Curly braces create a set when they contain values. Repeated values collapse immediately: ```python info = {"Carla", 19, False, 5.9, 19} print(info) ``` The printed order can vary, and the duplicate `19` appears only once: ```text {False, 19, 5.9, 'Carla'} ``` Do not build logic around that printed order. A set has no list-like index, so `info[0]` raises a `TypeError`. For an empty set, use `set()`, because `{}` creates an empty dictionary: ```python empty_set = set() empty_dictionary = {} print(type(empty_set).__name__) print(type(empty_dictionary).__name__) ``` The values inside a set must be hashable. Strings, numbers, tuples containing hashable values, and booleans work. A list does not: ```python values = {"ready", ["not", "hashable"]} ``` That code raises `TypeError: unhashable type: 'list'`. A mutable list could change after insertion, which would make its lookup position unreliable. If you need a fixed collection inside a set, use a tuple when its contents are hashable. ## Iterating and checking membership Iterate over a set with a `for` loop. Each value appears once, but the order may differ between runs: ```python info = {"Carla", 19, False, 5.9} for item in info: print(item) ``` The loop visits each value once. If you need predictable output for a report or a test, sort a compatible set first: ```text False 5.9 19 Carla ``` For a membership check, the syntax is much shorter: ```python allowed_roles = {"admin", "editor", "viewer"} role = "editor" if role in allowed_roles: print("access granted") ``` The set does not tell you where `"editor"` is. It answers whether the value belongs to the group. That is the contract you should design around. ## Combining groups Set operations use the same ideas you may have seen in mathematics. A union collects values from either set, an intersection keeps values found in both, and a difference keeps values found on one side only. The methods return new sets unless their name ends in `_update`. ### Union without changing either set `union()` returns a new set and leaves both inputs alone: ```python cities = {"Tokyo", "Madrid", "Berlin", "Delhi"} cities2 = {"Tokyo", "Seoul", "Kabul", "Madrid"} cities3 = cities.union(cities2) print(sorted(cities3)) print(sorted(cities)) ``` The first line is `['Berlin', 'Delhi', 'Kabul', 'Madrid', 'Seoul', 'Tokyo']`, while the second still contains only the original four cities. The `|` operator is a shorter spelling for the same non-mutating operation: ```python all_cities = cities | cities2 ``` When you do want to change `cities`, use `update()`: ```python cities = {"Tokyo", "Madrid", "Berlin", "Delhi"} cities2 = {"Tokyo", "Seoul", "Kabul", "Madrid"} cities.update(cities2) print(sorted(cities)) ``` Here `cities` itself has changed. This difference between a new result and an in-place update is worth checking before you pass a set into another function. ### Shared values with intersection `intersection()` keeps values found in both sets and returns a new set: ```python cities = {"Tokyo", "Madrid", "Berlin", "Delhi"} cities2 = {"Tokyo", "Seoul", "Kabul", "Madrid"} cities3 = cities.intersection(cities2) print(sorted(cities3)) ``` The output is `['Madrid', 'Tokyo']`. The `&` operator expresses the same idea as `cities & cities2`. The update form keeps only the shared values in the original set: ```python cities = {"Tokyo", "Madrid", "Berlin", "Delhi"} cities2 = {"Tokyo", "Seoul", "Kabul", "Madrid"} cities.intersection_update(cities2) print(sorted(cities)) ``` Now `cities` contains only the shared values. Use this form when you own the set and intentionally want to discard the other values. ### Values that belong to one side `symmetric_difference()` keeps values that belong to one set but not both: ```python cities = {"Tokyo", "Madrid", "Berlin", "Delhi"} cities2 = {"Tokyo", "Seoul", "Kabul", "Madrid"} cities3 = cities.symmetric_difference(cities2) print(sorted(cities3)) ``` The output is `['Berlin', 'Delhi', 'Kabul', 'Seoul']`. The `^` operator is the shorter form. If you call the update version, `cities` changes in place: ```python cities = {"Tokyo", "Madrid", "Berlin", "Delhi"} cities2 = {"Tokyo", "Seoul", "Kabul", "Madrid"} cities.symmetric_difference_update(cities2) print(sorted(cities)) ``` The result is the same four one-sided values, stored back in `cities`. ### Values missing from the other set `difference()` is directional. It keeps values in the first set that are missing from the second: ```python cities = {"Tokyo", "Madrid", "Berlin", "Delhi"} cities2 = {"Seoul", "Kabul", "Delhi"} cities3 = cities.difference(cities2) print(sorted(cities3)) ``` The output is `['Berlin', 'Madrid', 'Tokyo']`. Reversing the operands gives a different answer because `cities2 - cities` means something else. `difference_update()` stores the result back in the first set: ```python cities = {"Tokyo", "Madrid", "Berlin", "Delhi"} cities2 = {"Seoul", "Kabul", "Delhi"} cities.difference_update(cities2) print(sorted(cities)) ``` Now `cities` contains only cities that were not in `cities2`. You can also ask whether one set contains another with `issubset()` and `issuperset()`, or test whether two sets share nothing with `isdisjoint()`. These methods read like the question you are asking, which is usually better than writing a manual loop. My practical judgment is that sets are the right tool for membership and group comparison, and a bad tool for ordered output. If the order is part of the result, keep a list or convert the set to a sorted list at the boundary where you display it. The set should answer "does this belong?"; a list should answer "what comes next?". ![Thank you banner graphic for Python sets blog](https://images.pexels.com/photos/2072165/pexels-photo-2072165.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1) ## Docstrings in Python with Examples URL: https://www.swapnoneel.site/blog/docstrings-in-python Date: 2023-01-25T14:42:32.056Z Summary: Python stores a docstring on __doc__, where tools and people can read it. Learn how docstrings differ from comments and how to write useful ones. ## The string Python remembers A docstring is a string literal placed immediately after the definition of a module, class, function, or method. Python stores that string on the object's **doc** attribute. That is the difference between a docstring and an ordinary explanatory sentence in a comment: programs can read the docstring later. Start with the smallest useful example: ```python def square(number): """Return the square of number.""" return number ** 2 print(square(5)) print(square.__doc__) ``` The output is: ```text 25 Return the square of number. ``` The string does not print when square runs. It becomes metadata attached to square, and the second print asks for that metadata directly. You can inspect it in a Python shell without opening the function's source file. ## Position is part of the rule Python only treats the first string expression in a definition as its docstring. Put an assignment or another statement first, and the string remains an unused literal: ```python def wrong_order(): value = 10 """This text is not the function docstring.""" return value print(wrong_order.__doc__) ``` The output is None. The string is valid Python, but it is no longer in the position Python reserves for documentation. This is a small rule, and it is easy to break when adding setup code above a docstring. The same pattern works for classes and modules: ```python class Notebook: """Store notes in memory.""" def __init__(self): self.notes = [] print(Notebook.__doc__) ``` Here the docstring describes the class as a callable object type. A module docstring follows the module's opening comments and appears before other statements in the file. ## Comments answer a narrower question Use a comment to explain a line, a workaround, or a decision inside the implementation. Python does not attach comments to the function object. Use a docstring for the public behavior that a caller needs to understand. ```python def divide(total, count): """Return total divided by count. Raises ZeroDivisionError when count is zero. """ # The explicit check gives the caller a clear rule before division. if count == 0: raise ZeroDivisionError("count cannot be zero") return total / count ``` The comment explains why the check exists. The docstring explains what divide returns and what can go wrong for someone calling it. If the function is part of a package, that distinction becomes more useful because a reader may see the docstring in an editor or generated reference page without reading the source. ## Write for the next caller A useful docstring answers the questions that the function signature does not. What does the value mean? Does the function mutate an argument? Which errors should the caller handle? Does an empty input have a special result? ```python def average(values): """Return the arithmetic mean of a non-empty sequence of numbers. Raises ValueError when values is empty. """ if not values: raise ValueError("values must not be empty") return sum(values) / len(values) print(average([2, 4, 6])) ``` The first sentence is enough for a quick read. The second tells a caller why an empty list fails. You do not need to document every obvious line, and you should not turn every short function into a wall of labels that says less than one good sentence. For a public library, a longer format can be useful when the parameters and examples are genuinely hard to infer. Keep the format consistent with the project. The format matters less than the truth of the information inside it. ## Read the documentation while debugging The built-in help function reads docstrings: ```python help(average) ``` In an interactive shell, the result may open in a pager. Press q to leave the pager when you are done. You can also use **doc** when you want the raw string, or inspect the class and method that owns the documentation. One warning: inherited or decorated functions can make the source of a docstring less obvious. A decorator that does not preserve metadata may replace the original docstring with None or with the wrapper's text. functools.wraps helps when you write decorators, but the practical fix is still to check what help() shows for the callable a user actually receives. My caveat is that a docstring is part of the interface, not a comment dump. When the implementation changes, reread it as if you were a new caller. A short description that stays true is worth more than a detailed promise the function no longer keeps. ![Thank you card in blue tones](https://img.freepik.com/free-vector/thank-you-card-blue-tones_23-2148665027.jpg?w=1380&t=st=1674657663~exp=1674658263~hmac=4b0703b3e652f76dd18a3a9e99932842361293d22d9096d3abafe7d2f2280837) ## String Formatting in Python with Examples URL: https://www.swapnoneel.site/blog/string-formatting-in-python Date: 2023-01-25T07:50:52.768Z Summary: Python string formatting turns values into readable text. Compare str.format() and f-strings, then use format specifications for prices, percentages, and separators. Printing a value is easy. Printing it in the exact shape a person expects takes a little more thought. A price may need two decimal places, a percentage may need a percent sign, and a report line may need every column to line up. String formatting separates the message from the rule for displaying each value. Python gives you a few styles, but `str.format()` and f-strings cover most code you will write today. ## Using str.format() The `str.format()` method replaces placeholders inside a string. A format specification after the colon controls the output: ```python txt = "For only {price:.2f} dollars!" print(txt.format(price = 49)) ``` Here `price` is inserted with two digits after the decimal point, so the output is `For only 49.00 dollars!`. The original number remains an integer; only its text representation changes. The braces can contain a position or a name. Positional placeholders start at zero: ```python message = "{} scored {} points." print(message.format("Maya", 42)) ``` Named placeholders make a template easier to read when it has several values: ```python message = "{name} has {count} messages." print(message.format(name="Maya", count=42)) ``` The values do not need to be strings. Python converts them while building the final text, and the format specification tells it how to display a value. Named fields are especially useful when the same template is stored in one place and values arrive later. You can also align text and numbers: ```python rows = [("Tea", 3), ("Coffee", 12)] for name, count in rows: print("{:<10} {:>3}".format(name, count)) ``` The output is: ```text Tea 3 Coffee 12 ``` `<` aligns to the left and `>` aligns to the right. The `10` and `3` are field widths, not limits on the values. A long string can still spill past its field. ## Writing f-strings An f-string puts the letter `f` before the opening quote. Expressions inside `{}` are evaluated when Python creates the string: ```python name = "Tushar" age = 23 print(f"Hello, my name is {name} and I am {age} years old.") ``` The output is: ```text Hello, my name is Tushar and I am 23 years old. ``` You can put an expression inside the braces, not just a variable: ```python items = 4 price = 12.5 print(f"Total: {items * price:.2f}") ``` This prints: ```text Total: 50.00 ``` The part after the colon is a format specification. `.2f` asks for a floating-point value with two digits after the decimal point. Python evaluates `items * price` first, then applies `.2f` to the result. The same syntax handles percentages, alignment, and thousands separators: ```python completion = 0.875 total = 1250000 print(f"Completion: {completion:.1%}") print(f"Total: {total:,}") ``` This prints `Completion: 87.5%` and `Total: 1,250,000`. The stored values are still `0.875` and `1250000`; formatting changes what the reader sees. If you need a literal brace, double it: ```python name = "Maya" print(f"{{user}}: {name}") ``` The output is `{user}: Maya`. A single unmatched brace causes a `SyntaxError`, which is an annoying failure when a message is assembled from several pieces, so keep complicated templates readable. ## Which style should you use F-strings are usually easier to read when the values are already in scope. `str.format()` is still useful when the template is stored separately or when you want to pass named values explicitly. Older code may use percent formatting, such as `"Hello, %s" % name`. It still works, but several values and format rules become harder to read. When you control the Python version, reach for an f-string first, then use `str.format()` when the template and values need to stay separate. There is a safety boundary here. An f-string evaluates its Python expressions immediately, and formatting does not validate a value or escape it for HTML, SQL, or a shell command. Use the output library's escaping or parameter handling for those contexts. A nicely formatted string can still be unsafe input. My default is f-strings for local variables and calculations, `str.format()` for reusable templates, and neither one as a substitute for validation. Once you know whether you are changing the value or only changing its display, the syntax becomes much less mysterious. ![Thank you graphic for Python string formatting blog](https://images.pexels.com/photos/2072165/pexels-photo-2072165.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1)