The Ultimate Guide to the JavaScript Event Loop: Mastering Async in the Browser
Why Your 'Async' JavaScript Still Freezes The UI (And Why The Event Loop Is To Blame)
Most developers building their first or second SaaS product think they understand asynchronous JavaScript. They use async/await, promise chains, and callbacks. They read a few blog posts, watch a YouTube video, and feel confident. I was one of them. I shipped multiple products like Flow Recorder and Store Warden with this surface-level understanding. Then, the bug reports started rolling in. "UI freezes when loading large data," "Button clicks don't respond," "Dashboard takes forever to render." My "asynchronous" code was still blocking the main thread.
The shocking truth is: simply using async keywords does not guarantee non-blocking behavior. It's a common, expensive misconception. I learned this the hard way, debugging performance bottlenecks across different Shopify apps and custom WordPress plugins. I spent days, sometimes weeks, chasing phantom freezes in seemingly "optimized" code. The problem wasn't my Promise.all syntax or my await calls. The problem was my fundamental misunderstanding of the JavaScript Event Loop.
This isn't just about elegant code. It's about shipping a usable product. An unresponsive UI costs you users. It costs you money. I remember one critical bug in Trust Revamp, my Shopify reviews app. Customers reported that the app would hang for 5-10 seconds when trying to import thousands of reviews. My async functions were indeed fetching data concurrently, but the processing of that data, even after fetching, was still happening in a way that monopolized the main thread. The browser was effectively frozen. I initially blamed the database, then the API, then Shopify itself. The real culprit? My ignorance of how the JavaScript Event Loop actually orchestrates tasks.
My team and I eventually refactored major parts of Trust Revamp, meticulously breaking down CPU-intensive operations and scheduling them correctly. That experience was a wake-up call. Many developers, especially those from Dhaka like me, building for global audiences, often gloss over these low-level mechanics. We focus on frameworks and features, not runtime intricacies. This approach works until it doesn't. You need to know how the browser's JavaScript engine handles async operations. You must understand the Event Loop. Without it, you're just guessing, and those guesses will cost you dearly in performance, user experience, and ultimately, your product's success. This guide won't sugarcoat it. It will show you exactly what happens under the hood and how to avoid the expensive mistakes I made.
JavaScript Event Loop Explained in 60 seconds: The JavaScript Event Loop is the hidden orchestrator that allows a single-threaded language like JavaScript to perform non-blocking I/O operations. It continuously checks if the call stack is empty. If it is, the Event Loop moves functions from the task queue (also known as the callback queue or macrotask queue) onto the call stack for execution. This mechanism enables JavaScript to handle things like user interactions, network requests, and timers without freezing the main thread. It ensures your application remains responsive, even while waiting for long-running operations to complete. Microtasks, like Promise callbacks, are processed with higher priority than macrotasks, leading to subtle but critical execution order differences. Understanding this loop is crucial for writing high-performance, responsive web applications.
What Is JavaScript Event Loop Explained and Why It Matters
JavaScript is fundamentally a single-threaded language. This means it has one call stack and one memory heap. It can execute only one piece of code at a time. This design choice simplifies programming, but it immediately raises a critical question: How does JavaScript handle long-running operations like network requests, file I/O, or user interactions without freezing the entire browser? If your script spent 5 seconds fetching data from an API, your UI would be completely unresponsive during that time. That's a terrible user experience.
This is where the JavaScript Event Loop Explained comes into play. It's not part of the JavaScript language itself. Instead, it's a core component of the browser's runtime environment (or Node.js runtime). Think of it as a constantly running process that monitors two key things: the Call Stack and the Task Queue (sometimes called the Callback Queue or Macrotask Queue).
Let's break down the core components the Event Loop coordinates:
- The Call Stack: This is where your synchronous JavaScript code is executed. When a function is called, it's pushed onto the stack. When it returns, it's popped off. JavaScript executes code on the stack sequentially. If the call stack gets backed up with too many synchronous operations, your UI freezes. I've seen this happen countless times in my own projects, like Paycheck Mate, where complex calculations were initially run synchronously, making the app feel sluggish.
- Web APIs (Browser APIs): These are not part of the JavaScript engine but are provided by the browser. They include
setTimeout(),setInterval(), DOM events (click,scroll), and network requests (fetch,XMLHttpRequest). When you call one of these functions in your JavaScript code, the browser takes over. The function is pushed onto the call stack, then immediately popped off (as it's just a setup call), and the Web API starts its work in the background. This is the "asynchronous" part. - Task Queue (Macrotask Queue): Once a Web API completes its background work (e.g., a
setTimeouttimer expires, a network request returns data, or a DOM event fires), its associated callback function is not immediately executed. Instead, it's placed into the Task Queue. This queue holds "macrotasks" likesetTimeout,setInterval, I/O, and UI rendering events. - Microtask Queue: This is a higher-priority queue introduced with Promises and
async/await. Callbacks for resolved Promises (.then(),.catch(),.finally()) andawaitcontinuations go into the Microtask Queue. Crucially, the Event Loop processes all microtasks before moving to the next macrotask. This priority difference is a common trap for developers. I once spent a full day debugging why a UI update wasn't happening immediately after afetchcall, only to discover a chain of microtasks was delaying the macrotask responsible for rendering.
The JavaScript Event Loop's job is simple: It continuously checks if the Call Stack is empty. If the Call Stack is empty, it then looks at the Microtask Queue. If there are any microtasks, it moves all of them, one by one, to the Call Stack for execution until the Microtask Queue is empty. Only after the Microtask Queue is empty does the Event Loop check the Macrotask Queue. If there's a macrotask waiting, it moves one macrotask to the Call Stack for execution. This cycle repeats indefinitely. This mechanism is what enables the non-blocking behavior we rely on. Without it, my 8+ years of experience building scalable SaaS architecture would have been a constant battle against unresponsive UIs, especially when handling user data in real-time or processing large datasets for analytics on platforms like Flow Recorder. Understanding this loop is not just academic; it directly impacts how performant and responsive your applications feel to users. It determines whether your app is a joy to use or a source of frustration.
A Step-by-Step Framework for Event Loop Mastery
Understanding the JavaScript Event Loop is critical. It determines how your applications perform. It dictates user experience. I've learned this through years of debugging slow UIs. My 8+ years of experience building scalable SaaS architecture has shown me this repeatedly. This framework breaks down the Event Loop. You can use it to build more responsive applications.
1. Grasp the Call Stack's Role
The Call Stack is where synchronous JavaScript code executes. It's a LIFO (Last In, First Out) structure. When a function is called, it's pushed onto the stack. When it returns, it's popped off. JavaScript is single-threaded. This means only one function runs at a time. A long-running function here blocks everything. It freezes the UI. This is a common mistake I made early on with Flow Recorder's data processing. I blocked the stack for tens of seconds.
2. Identify Asynchronous Operations (Web APIs)
Asynchronous tasks don't run on the Call Stack directly. They are handled by Web APIs. These are browser-provided functionalities. Think setTimeout(), fetch(), DOM events like click. When you call one, the browser takes over. The actual work happens in the background. The JavaScript engine doesn't wait. It moves on. This is crucial for non-blocking behavior. Without Web APIs, no modern web application would function.
3. Differentiate Microtasks and Macrotasks
This is where many developers get confused. It's a key distinction.
- Microtasks: These have higher priority. They include Promise callbacks (
.then(),.catch(),.finally()) andasync/awaitcontinuations. They also includequeueMicrotask(). The Event Loop empties the entire Microtask Queue before processing any macrotasks. - Macrotasks (Tasks): These have lower priority. They include
setTimeout(),setInterval(), I/O operations, and UI rendering. The Event Loop processes one macrotask at a time. It then checks for microtasks. This priority order causes many unexpected behaviors. I once spent days figuring out why my UI updates were delayed. It was always a microtask chain I hadn't accounted for.
4. Visualize the Event Loop's Cycle
The Event Loop is a continuous process. Its job is simple: Keep the Call Stack empty and manage the queues.
- Check Call Stack: Is it empty? If not, keep running synchronous code.
- Check Microtask Queue: If the Call Stack is empty, move all pending microtasks to the Call Stack. Execute them one by one. Empty the queue completely.
- Check Macrotask Queue: If both Call Stack and Microtask Queue are empty, move one pending macrotask to the Call Stack. Execute it.
- Repeat: Go back to step 1. This cycle ensures responsiveness. It provides a structured way to handle concurrent operations.
5. Prioritize User Experience: Don't Block the Main Thread
This step is often overlooked in technical guides. But it's the most important. The main thread is responsible for UI rendering. It handles user input. If you block it with heavy computation, your UI freezes. Users get frustrated. They leave. My experience building apps like Store Warden for global audiences taught me this. A slow app loses users. Always offload heavy work. Use setTimeout(..., 0) to break tasks. Consider requestIdleCallback for non-essential work. Or use Web Workers for truly CPU-intensive operations. Your users don't care about your complex algorithms if they can't click a button.
6. Profile and Debug with Precision
Guessing at performance issues wastes time. Use browser developer tools. The Performance tab in Chrome DevTools is indispensable. It visualizes the Call Stack. It shows microtasks and macrotasks. You can see exactly what's blocking your UI. It pinpoints long-running functions. This tool helps you identify the root cause, not just the symptom. I use it constantly when optimizing new features for Trust Revamp.
7. Optimize for Responsiveness with Code Patterns
Implement patterns that respect the Event Loop.
- Chunk long computations: Instead of one big loop, break it into smaller parts. Run each part in a
setTimeout(task, 0). - Debounce/Throttle: For events like
scrollorresize, limit how often their handlers execute. Libraries like Lodash provide these utilities. - Use Web Workers: For truly heavy tasks, offload them to a separate thread. This keeps the main thread free.
- Understand Promise priority: Be aware that
.then()blocks UI updates until all microtasks are cleared. Don't chain too many synchronous operations within promises if UI responsiveness is critical. I've learned this lesson the hard way.
Real-World Event Loop Scenarios from My SaaS Journey
My journey building SaaS products from Dhaka has been a constant lesson in performance. The JavaScript Event Loop often decides success or failure. Here are some situations where understanding it made a tangible difference.
Example 1: Batch Processing in Flow Recorder
Setup: Flow Recorder helps e-commerce stores manage customer interactions. An early feature involved importing thousands of customer records from a CSV. Each record required validation and database insertion.
Challenge: I wrote a single function to read the CSV, parse it, and insert all records. It looked clean. I used a simple for loop. When a user uploaded a 5,000-row CSV, the UI froze completely for 45-60 seconds. The browser showed a "page unresponsive" warning. Users often refreshed or closed the tab. We saw a 20% bounce rate on this specific import page. It was a terrible user experience.
Went Wrong: The entire import process was synchronous. It ran entirely on the main thread. My for loop, parsing, and database calls (even if fetch was async, the setup of 5,000 fetch calls was synchronous) blocked the Call Stack. The Event Loop had no chance to process UI updates or user input. I assumed the browser would handle it. It didn't. I paid for that assumption with user frustration.
Action: I refactored the import logic. I broke the 5,000 records into chunks of 100. Each chunk was processed in a setTimeout(processChunk, 0). I used a recursive pattern: processChunk would process its 100 records, then schedule the next processChunk via setTimeout. I also added a progress bar that updated after each chunk completed.
Result: The UI remained fully responsive throughout the import. The progress bar advanced smoothly. The total import time increased slightly, from 45 seconds to about 55 seconds, due to the overhead of setTimeout scheduling. However, the perceived performance was dramatically better. User bounce rate on that page dropped to near zero. Users saw continuous feedback. They trusted the app. This simple change saved the feature. You can't put a price on user trust.
Example 2: Real-time Analytics Dashboard in Trust Revamp
Setup: Trust Revamp displays real-time analytics for customer reviews. The dashboard shows various charts and metrics that update frequently. We needed to push updates every few seconds.
Challenge: I initially used setInterval(fetchAndUpdateCharts, 3000) to poll our API every 3 seconds and re-render the D3.js charts. On dashboards with many complex charts or during peak traffic, the updates became visibly choppy. Sometimes, a new fetchAndUpdateCharts call would be queued before the previous one finished rendering. This led to visual glitches, missed data points, and a general feeling of sluggishness. The CPU usage spiked to 70-80% during these overlaps.
Went Wrong: setInterval does not guarantee that the previous execution has finished. It simply queues a new macrotask after a fixed delay. If the chart rendering took longer than 3 seconds, the Event Loop would queue another fetchAndUpdateCharts right after the first one started. This created a backlog of macrotasks. The browser struggled to keep up. I was effectively overwhelming the Event Loop.
Action: I switched from setInterval to a recursive setTimeout pattern. The fetchAndUpdateCharts function would:
- Fetch new data (async).
- Render charts (potentially long-running).
- Only then schedule the next
setTimeout(fetchAndUpdateCharts, 3000). This ensured that a new update cycle only began after the previous one fully completed. I also implemented a simple debounce mechanism for incoming data streams to prevent too-rapid UI updates.
Result: The dashboard updates became perfectly smooth. Even with high data volumes, the UI never froze. CPU usage stabilized at 20-30% during updates. The charts rendered without glitches. The perceived "real-time" feel improved significantly. This small change, understanding the difference between setInterval and recursive setTimeout in the context of the Event Loop, made Trust Revamp's analytics dashboard truly usable and scalable. It could handle 3x the previous data volume without issues.
Avoiding Common JavaScript Event Loop Pitfalls
I've made my share of mistakes with the JavaScript Event Loop. These errors cost me time, money, and user satisfaction. Learning from them helps you avoid the same traps.
Blocking the Main Thread with Long Synchronous Tasks
Mistake: Running a complex calculation or a large loop directly on the main thread. This prevents the browser from updating the UI or responding to user input.
Fix: Break up long-running tasks into smaller chunks. Use setTimeout(chunkFunction, 0) or requestIdleCallback to yield control back to the Event Loop.
Misunderstanding Microtask Priority
Mistake: Expecting UI updates or setTimeout callbacks to run immediately after a Promise resolves. Microtasks (promises, async/await) always run before any macrotasks (UI rendering, setTimeout).
Fix: Remember that all microtasks complete before the Event Loop checks the macrotask queue. Schedule UI-critical updates in a separate macrotask if they must follow a heavy microtask chain.
Over-reliance on setTimeout(fn, 0) for Immediate Execution
Mistake: Thinking setTimeout(fn, 0) executes fn "as soon as possible." While it pushes fn to the macrotask queue quickly, any pending microtasks will run first. This can delay execution more than expected. This sounds like good advice, but it's often misapplied.
Fix: Use Promise.resolve().then(fn) if you need fn to execute in the next microtask tick. This provides a higher priority "next tick" behavior than setTimeout(fn, 0).
Infinite Loops in Microtasks
Mistake: Creating promise chains that continuously resolve or reject without yielding control. This can starve the macrotask queue. It prevents UI updates and other browser tasks.
Fix: Ensure promise chains have an end state. Avoid recursive .then() calls that don't allow the Event Loop to breathe.
Ignoring Browser DevTools for Performance Profiling
Mistake: Guessing at performance bottlenecks. Relying on anecdotal evidence or superficial observations. Fix: Consistently use the Performance tab in Chrome DevTools. It visually maps the Call Stack, microtasks, macrotasks, and rendering. It shows you exactly what's taking time.
Not Debouncing/Throttling Event Handlers
Mistake: Attaching raw event listeners to high-frequency events like scroll, resize, or mousemove. Each event fires a separate macrotask, overwhelming the Event Loop.
Fix: Wrap these event handlers with debounce or throttle utilities. This limits how often the callback function actually executes.
Essential Tools and Resources for Event Loop Debugging
Navigating the JavaScript Event Loop effectively requires the right tools. I've used all of these to troubleshoot performance issues in my products like Paycheck Mate and Custom Role Creator.
| Tool/Resource | Purpose | Why it's useful (or not) |
|---|---|---|
| Chrome DevTools (Performance) | Visualizes call stack, tasks, rendering, CPU | Underrated: It's built-in, powerful, and provides deep insights into the exact order of execution and what's blocking the main thread. Many developers don't fully leverage its event loop tracing capabilities. Essential for real-world debugging. |
Node.js perf_hooks module | Measures execution time and performance in Node.js | Critical for server-side event loop bottlenecks. Helps pinpoint synchronous blocks that starve the Node.js event loop. |
| Lighthouse | Audits web page performance and accessibility | Overrated (for deep event loop debugging): Provides high-level scores and recommendations. It tells you if you have a performance problem, but not why at the granular event loop level. You still need DevTools for the root cause. |
async/await | Simplifies asynchronous code with Promises | Makes complex asynchronous flows readable and maintainable. It's a language feature that implicitly manages microtasks. |
requestIdleCallback() | Schedules low-priority work during browser idle | Prevents UI jank for non-critical, background tasks. It's an explicit way to tell the browser: "run this when you have nothing else to do." |
| Web Workers | Runs scripts in background threads | Offloads CPU-intensive tasks entirely from the main thread. Ideal for complex calculations, image processing, or large data transformations. This is true concurrency for JavaScript. |
| Loupe (by Philip Roberts) | Interactive JavaScript Event Loop visualization | An excellent online tool for learning and understanding the concepts visually. It helps build an intuitive mental model of the Call Stack, Web APIs, and queues. Not for debugging production code, but invaluable for education. |
| MDN Web Docs (Event Loop) | Comprehensive documentation on the Event Loop | The definitive source for understanding the specifications and nuances of the JavaScript Event Loop. I often go back to MDN's Event Loop documentation for clarification. |
Advanced Insights and Event Loop Optimizations
The JavaScript Event Loop is foundational. My 8 years in software development, including building products for global users from Dhaka, taught me its true power and its subtle traps.
One finding particularly surprised me. It contradicts common advice. Many developers believe setTimeout(fn, 0) guarantees fn runs "as soon as the current script finishes." This is not entirely true. It means fn gets queued as a macrotask. But the Event Loop prioritizes all microtasks before touching the macrotask queue. So, if your code generates a long chain of promises, setTimeout(fn, 0) might execute much later than expected. It waits for every .then() and await to complete first. This was a critical lesson. It changed how I structure time-sensitive UI updates in my Shopify apps.
Responsive UIs are not just "nice to have." They are a business imperative. Google's Core Web Vitals, specifically First Input Delay (FID), directly measures the responsiveness to user input. A poor FID score, often caused by a blocked main thread and event loop mismanagement, significantly impacts SEO and user perception. Research by Google shows that improving Core Web Vitals can lead to better user engagement and conversion rates. A slow UI costs you money.
Here's a breakdown of the impact:
| Pros of a Well-Managed Event Loop | Cons of a Poorly-Managed Event Loop |
|---|---|
| Responsive UI: Smooth animations, instant feedback. | Frozen UI: Unresponsive, janky user experience. |
| Better User Experience: Users feel in control. | High Bounce Rates: Users abandon slow apps. |
| Higher Conversion Rates: Users complete tasks. | Frustrated Users: Negative brand perception. |
| Efficient Resource Utilization: Browser works optimally. | Difficult Debugging: Hard to pinpoint blocks. |
| Improved Core Web Vitals: Better SEO ranking. | Poor Core Web Vitals: Negative SEO impact. |
| Scalable Applications: Handles complex operations. | Unscalable Architecture: Crumbles under load. |
Mastering the JavaScript Event Loop is not just about avoiding errors. It's about designing applications that feel fast. It's about building user trust. It's about delivering robust software. My AWS Certified Solutions Architect (Associate) knowledge reinforces this. Performance is a feature. It's not an afterthought. It's built into the core understanding of how JavaScript works.
From Knowing to Doing: Where Most Teams Get Stuck
You now understand the JavaScript Event Loop. You grasp microtasks, macrotasks, and the call stack. But knowing this isn't enough. Execution is where most teams, including ones I've led in Dhaka, consistently fail. I’ve seen it firsthand. We understood the theory perfectly. Yet, our Shopify app, Store Warden, still had UI jank. Our WordPress plugins, like Custom Role Creator, lagged under load.
The manual way—meticulously tracing execution, adding setTimeout(0) calls, or debating requestAnimationFrame—works. Temporarily. But it's slow. It's error-prone. It doesn't scale when your codebase grows, or when a new developer joins. I learned this the hard way on a critical project, Trust Revamp. We spent weeks debugging performance issues that stemmed directly from a poor understanding of event loop interactions. The cost wasn't just development time. It was lost client trust, delayed launches, and a full rewrite of a core module. That mistake cost us close to $15,000 in opportunity and rework.
The real insight? The Event Loop isn't just an engine; it's a contract. You're signing up for asynchronous behavior. If you don't design your application to honor that contract from the start, you'll pay the price later. I've built scalable SaaS architecture for 8+ years, and this principle holds true across Laravel, Node.js, and Python. It's not about optimizing a single function. It's about designing your entire system to be non-blocking by default. That's the difference between a project that scales effortlessly and one that constantly fights its own foundation.
Want More Lessons Like This?
I don't just share successes. I share the expensive lessons, the late nights, and the critical mistakes that shaped my journey as a full-stack engineer and AWS Certified Solutions Architect. Join me as I build products and navigate the complex world of tech from Dhaka.
Subscribe to the Newsletter - join other developers building products.
Frequently Asked Questions
Why does understanding the JavaScript Event Loop matter for my project?
Understanding the JavaScript Event Loop is critical for building responsive, performant web applications. Without it, you’ll encounter UI freezes, slow animations, and inefficient data processing. On a project like Flow Recorder, where real-time responsiveness is key, a deep grasp of the event loop allowed me to process user interactions and background tasks without blocking the main thread. It prevents costly re-architectures later on.Is the Event Loop really that complex, or is it just overhyped?
It's not overhyped; it's fundamentally different from synchronous execution models. The complexity often comes from the interaction between microtasks, macrotasks, `requestAnimationFrame`, and browser rendering. Many developers, even experienced ones, treat JavaScript as purely synchronous until they hit a blocking issue. I've seen teams struggle for days with performance bottlenecks that boiled down to a misunderstanding of how the browser prioritizes tasks. It's a foundational concept for modern web development.How long will it take to apply these Event Loop principles to my existing codebase?
The time required depends heavily on your codebase's current state and size. For a small project or a specific module, you might see improvements in a few hours of refactoring. For a large, legacy application, it could take weeks or even months to systematically identify and address blocking operations. I usually start with critical user paths and performance hotspots. My experience building scalable SaaS architecture taught me to prioritize impact over total overhaul.Where should I start if I want to debug Event Loop related issues in my app?
Start with your browser's developer tools. Chrome's Performance tab is invaluable. Record a user interaction or a problematic sequence. Look for long tasks, layout thrashing, or excessive script execution on the main thread. Pay close attention to call stacks during these periods. I often use `console.time` and `console.timeEnd` around suspicious functions. Understanding [MDN's guide on the Event Loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop) is a solid theoretical foundation.Does this apply to server-side Node.js as well, or just browser JavaScript?
Absolutely, it applies to server-side Node.js. Node.js is built on the same non-blocking, event-driven architecture. The core principles of the Event Loop are identical. The difference lies in the types of tasks: instead of UI rendering, you're dealing with I/O operations, database queries, and network requests. Blocking the Node.js Event Loop means your server can't process other incoming requests, leading to severe performance degradation. My experience with Python (Flask/FastAPI) and PHP (Laravel) has shown me that understanding asynchronous patterns is universal for performance.What's one common mistake developers make regarding the Event Loop?
One pervasive mistake is performing heavy synchronous computations on the main thread, expecting the browser or Node.js to magically handle it. Another is misusing `setTimeout(0)` as a silver bullet; it pushes a task to the *end* of the macrotask queue, not necessarily prioritizing it. I've seen developers from Dhaka and abroad struggle with `Promise` resolution order, assuming microtasks always execute before the next render frame. Always remember: microtasks run *before* the next macrotask and *before* the browser renders. For more, check out [Jake Archibald's deep dive](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/).How does this relate to modern frameworks like React, Vue, or Svelte?
Modern frameworks abstract away much of the direct Event Loop manipulation, but they don't eliminate its importance. React's Fiber architecture, for instance, uses a scheduler to break down rendering work into smaller chunks, yielding to the browser periodically. Vue and Svelte also optimize updates to minimize blocking. However, if your component logic itself contains a long-running synchronous task, it will still block the Event Loop, regardless of the framework. You still need to manage your own expensive computations asynchronously.The Bottom Line
You've moved past merely knowing what the JavaScript Event Loop is to understanding its profound impact on application performance. The single most important thing you can do today is to identify one function in your codebase that performs a heavy, synchronous operation. Refactor it to be asynchronous using Promise, async/await, or web workers. Don't just read about it; implement it. See the difference yourself. If you want to see what else I'm building, you can find all my projects at besofty.com. This small change will not just improve your app; it will fundamentally shift how you approach writing performant JavaScript code.
Ratul Hasan is a developer and product builder. He has shipped Flow Recorder, Store Warden, Trust Revamp, Paycheck Mate, Custom Role Creator, and other tools for developers, merchants, and product teams. All his projects live at besofty.com. Find him at ratulhasan.com. GitHub LinkedIn