<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Thet Paing Phyo on Medium]]></title>
        <description><![CDATA[Stories by Thet Paing Phyo on Medium]]></description>
        <link>https://medium.com/@thet.paing.phyo_72486?source=rss-a84ca2df0b8c------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*dmbNkD5D-u45r44go_cf0g.png</url>
            <title>Stories by Thet Paing Phyo on Medium</title>
            <link>https://medium.com/@thet.paing.phyo_72486?source=rss-a84ca2df0b8c------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 23 May 2026 16:03:26 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@thet.paing.phyo_72486/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[System Design for Absolute Beginners: Lesson 2]]></title>
            <link>https://medium.com/@thet.paing.phyo_72486/system-design-for-absolute-beginners-lesson-2-cfccd34c5145?source=rss-a84ca2df0b8c------2</link>
            <guid isPermaLink="false">https://medium.com/p/cfccd34c5145</guid>
            <category><![CDATA[horizontal-scaling]]></category>
            <category><![CDATA[statelessserver]]></category>
            <category><![CDATA[load-balancer]]></category>
            <category><![CDATA[beginner]]></category>
            <category><![CDATA[vertical-scaling]]></category>
            <dc:creator><![CDATA[Thet Paing Phyo]]></dc:creator>
            <pubDate>Tue, 21 Apr 2026 16:14:23 GMT</pubDate>
            <atom:updated>2026-04-21T16:14:23.792Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>Before we start: I’m still learning system design myself. I wrote this post to organize what I learned in my own words and make it easier for other beginners to follow along. So this is not an expert guide. It’s a beginner learning out loud.</blockquote><p>When people first learn backend systems, one server feels like enough.</p><p>And at first, it often is.</p><p>A client sends a request.<br>The server handles it.<br>The server reads or writes data in the database.<br>Then it sends a response back.</p><p>That simple model is a great place to start.</p><p>But it does not stay enough forever.</p><p>This lesson explains the basic ideas behind scaling in a very practical way:</p><ul><li>why one server eventually becomes a problem</li><li>the difference between vertical and horizontal scaling</li><li>what a load balancer does</li><li>why stateless servers matter</li><li>whether bottlenecks appear</li><li>how a small to-do app can grow from one server to multiple servers</li></ul><h3>Why one server is not enough forever</h3><p>A single server is simple.</p><p>That is its biggest strength.</p><p>There are fewer moving parts, fewer things to manage, and fewer places for bugs to hide. For a small product, this is often the right starting point.</p><p>But one server has two big limits.</p><h4>1. It has limited capacity</h4><p>A server can only handle so much work.</p><p>It has limited:</p><ul><li>CPU</li><li>memory</li><li>disk</li><li>network capacity</li></ul><p>As more users arrive, more requests hit that server. Eventually it gets overloaded.</p><p>When that happens, the app may:</p><ul><li>become slow</li><li>start timing out</li><li>fail requests</li><li>crash</li></ul><p>So the first problem is simple: one machine cannot handle unlimited traffic.</p><h4>2. It is a single point of failure</h4><p>If your whole app depends on one server, that one server becomes critical.</p><p>If it crashes, the whole app is down.</p><p>That means even if performance is fine today, reliability is still fragile.</p><p>A simple analogy is a store with one cashier.</p><p>When there are only a few customers, one cashier is fine.<br>When the store gets busy, the line grows.<br>If the cashier leaves, nobody gets served.</p><p>That is the core scaling problem.</p><h3>Vertical scaling vs horizontal scaling</h3><p>Once one server is not enough, there are two basic ways to grow.</p><h4>Vertical scaling</h4><p>Vertical scaling means making one server stronger.</p><p>You keep one machine, but upgrade it:</p><ul><li>more CPU</li><li>more memory</li><li>faster hardware</li></ul><p>You are scaling “up”.</p><p>This is often the easiest first step because the architecture does not change much.</p><p>You do not need more app servers.<br>You do not need request routing between servers.<br>You do not need as much operational complexity.</p><p>but vertical scaling has limits.</p><p>You can only make one machine so big.<br>And even after upgrading it, it is still one machine.<br>So you still have a single point of failure.</p><h4>Horizontal scaling</h4><p>Horizontal scaling means adding more servers.</p><p>Instead of one machine doing all the work, several machines share it.</p><p>You are scaling “out”.</p><p>For example, instead of:</p><ul><li>1 app server</li></ul><p>you move to:</p><ul><li>2 app servers</li><li>3 app servers</li><li>10 app servers</li></ul><p>This helps with both traffic and reliability.</p><p>More traffic can be spread across multiple machines.<br>If one server dies, others may still keep the app running.</p><p>But horizontal scaling introduces new problems:</p><ul><li>how requests get distributed</li><li>how servers stay consistent</li><li>where shared state lives</li><li>how bottlenecks move elsewhere</li></ul><p>So horizontal scaling is more powerful, but also more complex.</p><h4>The key difference</h4><p>Vertical scaling: <strong>one stronger server</strong><br>Horizontal scaling: <strong>more servers</strong></p><p>That is the heart of it.</p><h3>What a load balancer is</h3><p>Once you have multiple app servers, a new question appears:</p><p>Which server should handle each request?</p><p>That is the job of the load balancer.</p><p>A load balancer sits in front of your app servers.</p><p>The client sends a request to the load balancer.<br>The load balancer decides which app server should receive it.<br>Then that server handles the request.</p><p>So the load balancer acts like a traffic director.</p><h4>Why it matters</h4><p>Without a load balancer, clients would need some other way to choose a server. That gets messy quickly.</p><p>A load balancer gives the system one front door.</p><p>It helps by:</p><ul><li>distributing traffic across servers</li><li>preventing one server from taking all requests</li><li>avoiding unhealthy or dead servers</li></ul><p>The important idea is not “perfectly even traffic”.<br>The important idea is request routing.</p><p>A useful analogy is a restaurant host.</p><p>Customers arrive at the entrance.<br>The host decides where they go.<br>The host is not cooking the food.<br>The host is directing traffic.</p><p>That is what a load balancer does for servers.</p><h3>Why stateless servers matter when scaling</h3><p>When beginners hear “stateless”, it often sounds abstract.</p><p>It is actually a practical idea.</p><h4>What is a state?</h4><p>State is information that must be remembered.</p><p>Examples:</p><ul><li>whether a user is logged in</li><li>shopping cart contents</li><li>to-do items</li><li>user preferences</li><li>profile data</li></ul><p>Some state is temporary while processing one request.<br>That is normal.</p><p>The real problem is <strong>important state that must still exist for later requests</strong>.</p><h4>What is a stateless server?</h4><p>A stateless server does not depend on its own memory to remember important data across requests.</p><p>That does not mean the server never uses memory.</p><p>it does.</p><p>It means the server does not keep important long-lived state only inside itself and assume the next request will come back to the same machine.</p><p>Instead, important shared state usually lives in a place all app servers can access, such as:</p><ul><li>a database</li><li>a cache</li><li>a session store</li></ul><h4>Why this matter with multiple servers</h4><p>In a scaled system, a user’s first request might go to Server A.<br>Their next request might go to Server B.</p><p>If Server A kept important user state only in its own memory, Server B may not know it.</p><p>Then the system behaves strangely.</p><p>For example:</p><ul><li>user logs in through Server A</li><li>login state lives only in Server A’s memory</li><li>next request goes to Server B</li><li>Server B does not know that user is logged in</li></ul><p>Now the user appears logged out or gets inconsistent behavior.</p><p>That is why stateless app servers are so helpful when scaling horizontally.</p><p>They make it safe for any request to go to any server.</p><p>A good rule is:</p><p><strong>Important long-lived state should not live only in one app server’s memory.</strong></p><h3>Common bottlenecks in a simple system</h3><p>A bottleneck is the part of the system that limits the whole system.</p><p>Even if everything else is fast, the overloaded part slows everything down.</p><p>Think of a narrow part of a road.<br>Cars can move quickly before and after it, but traffic still gets stuck there.</p><p>In a simple system, these are common bottlenecks.</p><h4>1. The app server</h4><p>If one app server gets too many requests, it may run out of CPU or memory.</p><p>That can cause:</p><ul><li>slow responses</li><li>crashes</li><li>timeouts</li></ul><p>This is often the first bottleneck in a small app.</p><h4>2. The database</h4><p>The database is one of the most common bottlenecks.</p><p>Why?</p><p>Because many requests need it.<br>Reads and writes often depend on it.<br>As traffic grows, the database can become overloaded.</p><p>That leads to:</p><ul><li>slow queries</li><li>delayed responses</li><li>a slow app overall</li></ul><h4>3. The network</h4><p>Sometimes the issue is not CPU or the database.</p><p>Sometimes data just moves too slowly across the network.</p><p>This can matter more when:</p><ul><li>there is lots of traffic</li><li>large files are being transferred</li><li>services call each other often</li></ul><h4>4. Disk or storage</h4><p>Reading and writing from storage can also become slow, especially with heavy writes or large amounts of data.</p><h4>5. A shared dependency</h4><p>This is an important scaling lesson:</p><p>Adding more app servers does not automatically fix the whole system.</p><p>Why not?</p><p>Because those app servers may all depend on one shared thing:</p><ul><li>one database</li><li>one cache</li><li>one file store</li></ul><p>That shared dependency may become the next bottleneck.</p><p>So scaling is not just about adding machines.<br>It is about finding the actual limiting part.</p><p>A very useful rule is:</p><p><strong>Your system is only as strong as its most overloaded important part.</strong></p><h3>A small example: scaling a to-do app</h3><p>Let’s walk through a very small example.</p><h4>Stage 1: one server</h4><p>At the beginning, a to-do app might look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Umg_n-Y_baZxWZibkzvjRg.png" /><figcaption>Client &gt; App Server &gt; Database</figcaption></figure><p>This is enough for basic features:</p><ul><li>create a to-do</li><li>list to-dos</li><li>mark a to-do complete</li><li>delete a to-do</li></ul><p>This is a good MVP design.</p><p>It is simple.<br>It is easier to build.<br>It is easier to debug.<br>It is the right place to start for many apps.</p><h4>What goes wrong as usage grows</h4><p>Over time, more users start using the app.</p><p>Now the single app server must handle more requests:</p><ul><li>more people logging in</li><li>more reads for to-do lists</li><li>more writes for creating and updating tasks</li></ul><p>Eventually that one server becomes overloaded.</p><p>So we need to scale.</p><h4>Stage 2: add multiple app servers</h4><p>Now the design becomes:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y0TngBDGBiVavABQrHVpCA.png" /></figure><p>You now have:</p><ul><li>1 load balancer</li><li>2 app servers</li><li>1 shared database</li></ul><p>The load balancer receives requests and sends them to one of the app servers.</p><p>This helps spread the work.</p><h4>Why statelessness matters here</h4><p>Suppose a user sends two requests:</p><ol><li>create a to-do</li><li>list all to-dos</li></ol><p>The first request might go to Server A.<br>The second might got to Server B.</p><p>That is fine <strong>if important state is shared</strong>.</p><p>Both servers can read the same data from the database.</p><p>That is why the app servers should be stateless.</p><p>If one server stored important request state only in its own memory, the next server might not know it.</p><h4>What improved?</h4><p>The app can now handle more application traffic because the request-processing work is shared across multiple servers.</p><h4>What did not automatically improve?</h4><p>The database is still shared.</p><p>And now both app servers send work to that same database.</p><p>So after scaling the app layer, the database may become the next bottleneck.</p><p>This is a very common pattern:</p><ol><li>one app server becomes overloaded</li><li>add more app servers</li><li>the database becomes the new limit</li></ol><p>So the system is better than before, but it is not infinitely scalable.</p><h3>The big lesson behind scaling basics</h3><p>Scaling is not magic.</p><p>It is not just “add servers”.</p><p>Real scaling means asking:</p><ul><li>what is the current bottleneck?</li><li>what problem are we solving?</li><li>what new problems will this change introduce?</li></ul><p>A beginner mistake is thinking that more servers always fix the system.</p><p>They do not.</p><p>They only help if the current bottleneck is the app server layer.</p><p>If the real bottleneck is the database, network, or disk, then adding app servers may do very little.</p><p>That is why good system design starts with simple architecture, then scales carefully based on an actual limits.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cfccd34c5145" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[System Design for Absolute Beginners: Lesson 1]]></title>
            <link>https://medium.com/@thet.paing.phyo_72486/system-design-for-absolute-beginners-lesson-1-a7d67fda2ad5?source=rss-a84ca2df0b8c------2</link>
            <guid isPermaLink="false">https://medium.com/p/a7d67fda2ad5</guid>
            <category><![CDATA[beginner]]></category>
            <category><![CDATA[self-improvement]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[design-systems]]></category>
            <category><![CDATA[learning-journey]]></category>
            <dc:creator><![CDATA[Thet Paing Phyo]]></dc:creator>
            <pubDate>Wed, 15 Apr 2026 13:44:22 GMT</pubDate>
            <atom:updated>2026-04-15T13:44:22.407Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>Before we start: I’m still learning system design myself. I wrote this post to organize what I learned in my own words and make it easier for other beginners to follow along. So this is not an expert guide. It’s a beginner learning out loud.</blockquote><p><a href="https://medium.com/@thet.paing.phyo_72486/building-a-tiny-to-do-app-to-understand-client-server-api-and-database-basics-625adbd936a9">Building a Tiny To-Do App to Understand Client, Server, API, and Database Basics</a></p><p>When people hear system design, they often imagine giant distributed systems, millions of users, and complicated diagrams.</p><p>This is not where I would start.</p><p>I would start with a much simpler question:</p><p><strong>What actually happens when a user does something in an app?</strong></p><p>If you understand that clearly, you already have the foundation of system design.</p><h3>Start with a simple example</h3><p>Imagine a user opens a to-do app and clicks:</p><p><strong>Add task</strong></p><p>They type:</p><p><strong>Buy milk</strong></p><p>What happens next?</p><p>At a basic level:</p><ol><li>The app sends a request</li><li>The server receives it</li><li>The server may store the task in a database</li><li>The server sends a response back</li><li>The app updates the screen</li></ol><p>That simple flow is the core of many software systems.</p><h3>Client and server</h3><p>The first two ideas to understand are <strong>client</strong> and <strong>server</strong>.</p><p><strong>Client<br></strong>The client is the part the user interacts with.</p><p>Examples:</p><ul><li>a browser</li><li>a mobile app</li><li>a desktop app</li></ul><p>The client collects input and shows results.</p><p><strong>Server<br></strong>The server is the part that does the work.</p><p>It usually:</p><ul><li>receives requests</li><li>validates data</li><li>applies rules</li><li>reads or writes data</li><li>sends response back</li></ul><p>A useful beginner way to think about it is:</p><ul><li><strong>client = where the user interacts</strong></li><li><strong>server = where the system logic lives</strong></li></ul><p>That distinction matters.</p><p>A client is not just “the thing that sends first.”<br>A server is not just “the thing that replies.”</p><p>The real difference is their role in the system.</p><h3>Request and response</h3><p>When the client wants something, it sends a <strong>request</strong>.</p><p>Examples:</p><ul><li>log me in</li><li>get my tasks</li><li>create a task</li></ul><p>The server process that request and sends back a <strong>response</strong>.</p><p>Examples:</p><ul><li>success</li><li>failure</li><li>data</li><li>error message</li></ul><p>This pattern is everywhere:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Hw1AedIyjPKsdZYIfmeLzg.png" /></figure><p>if you open an app and load your tasks, that is a request.<br>If the app shows your tasks after that, that came from a response.</p><p>This is one of the most important basic patterns in backend systems.</p><h3>Why a database exists</h3><p>A beginner might think:</p><p>“The server receives requests, so why not just keep everything there?”</p><p>Because server memory is temporary.</p><p>If the server crashes or restarts, data stored only in memory can disappear.</p><p>That is why important data is usually stored in a <strong>database</strong>.</p><p>A database gives the system <strong>durable storage</strong>.</p><p>That means important data can survive restarts.</p><p>Examples of data that should not disappear:</p><ul><li>users</li><li>tasks</li><li>orders</li><li>messages</li><li>account settings</li></ul><p>So the basic flow becomes:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DY0ua86Xir-NtupEJp4igw.png" /></figure><p>The server handles the request, but the database stores important data durably.</p><h3>Example: creating a task</h3><p>Let’s walk through a basic flow.</p><p>A user types:</p><p><strong>Buy milk</strong></p><p>Then:</p><ol><li>The client collects the task text</li><li>The client sends a request to the server</li><li>The server receives the request</li><li>The server validates the request</li><li>The server tells the database to store the task</li><li>The database saves it</li><li>The server sends a response back</li><li>The client updates the UI</li></ol><p>That is a complete beginner-friendly system flow.</p><p>It is simple, but it contains the core of system design thinking.</p><h3>The server does more than forward data</h3><p>Another common beginner misunderstanding is this:</p><blockquote>The server is just middleman.</blockquote><p>Not really.</p><p>The server often contains the application’s rules.</p><p>For example, during user signup, the server might:</p><ul><li>validate the email format</li><li>check whether the password is strong enough</li><li>check whether the email is already registered</li><li>hash the password before storage</li><li>save the user only if the input is valid</li></ul><p>So the server is not just passing data around.</p><p>It is deciding:</p><ul><li>what is valid</li><li>what is allowed</li><li>what gets stored</li><li>what gets rejected</li></ul><p>This is a huge idea.</p><p>The server is where much of the system’s logic lives.</p><h3>What an API is</h3><p>Now we can name the way the client talks to the server.</p><p>That is usually an <strong>API.</strong></p><p>A simple definition:</p><p><strong>An API is the way a client talks to a server.</strong></p><p>It defines:</p><ul><li>what requests can be made</li><li>what data should be sent</li><li>what responses come back</li></ul><p>You can think of it like a menu.</p><p>A menu tells you what you can order.<br>An API tells the client what it can ask the server to do.</p><p>For a to-do app, an API might allow:</p><ul><li>creating a task</li><li>listing tasks</li><li>deleting a task</li></ul><p>So an API is not magic.</p><p>It is just the contract between client and server.</p><h3>Why the client is untrusted</h3><p>This is one of the most important beginner ideas in backend design.</p><p><strong>The client is untrusted.</strong></p><p>Why?</p><p>Because the user controls it.</p><p>A user can:</p><ul><li>modify requests</li><li>fake inputs</li><li>bypass UI restrictions</li><li>use scripts or bots</li><li>send data the frontend never intended to allow</li></ul><p>That means the server must not blindly trust the client.</p><p>For example:</p><ul><li>the client should not decide whether a login is valid</li><li>the client should not decide whether a user is an admin</li><li>the client should not decide the real price of a product</li></ul><p>The server must validate important things itself.</p><p>A simple rule:</p><p><strong>Never trust the client for important logic.</strong></p><p>This rule shows up again and again in system design.</p><h3>Reads and writes</h3><p>Another useful basic distinction is <strong>read vs write.</strong></p><p><strong>Read</strong></p><p>A read fetches existing data.</p><p>Examples:</p><ul><li>get all my tasks</li><li>load my profile</li><li>fetch messages</li></ul><p><strong>Write</strong></p><p>A write changes data.</p><p>Examples:</p><ul><li>create a task</li><li>update profile</li><li>send a message</li><li>delete a comment</li></ul><p>This matters because systems often behave differently depending on whether they are read-heavy or write-heavy.</p><p>For now, the important thing is just understanding the distinction.</p><p>If the system is fetching data, that is a read.<br>If it is changing the data, that is a write.</p><h3>Why important state should not live in one server’s memory</h3><p>As systems grow, they often use more than one server.</p><p>That creates a new problem.</p><p>Suppose important user data lives only in one server’s memory.</p><p>What happens if:</p><ul><li>the next request goes to a different server?</li><li>that original server crashes?</li><li>the server restarts?</li></ul><p>The data may be missing.</p><p>That is why important state usually should not live only inside one server’s local memory.</p><p>This is one reason people talk about <strong>stateless servers.</strong></p><p>A stateless server does not depend on its own local memory to remember important things between requests.</p><p>That makes it easier to:</p><ul><li>add more servers</li><li>replace failed servers</li><li>distribute traffic safely</li></ul><p>At beginner level, the main takeaway is simple:</p><p><strong>Important long-term data should not depend on one server’s memory.</strong></p><h3>A clean mental model</h3><p>By the end of this lesson, the basic model looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DY0ua86Xir-NtupEJp4igw.png" /></figure><p>And each part has a role:</p><ul><li><strong>Client</strong>: collects user input and displays results</li><li><strong>Server</strong>: validates requests, applies logic, and decides what happens</li><li><strong>Database</strong>: stores important data durably</li><li><strong>API</strong>: the contract the client uses to talk to the server</li></ul><p>This the heart of many backend systems.</p><h3>After learning these ideas, you should be able to explain:</h3><ul><li>what a client is</li><li>what a server is</li><li>what request/response means</li><li>why a database is needed</li><li>what an API is</li><li>why the client is untrusted</li><li>the difference between reads and writes</li><li>why important data should not live only in one server’s memory</li></ul><p>That is not “advanced distributed systems.”</p><p>But it is real system design foundation.</p><p>And without it, more advanced topics will just feel like random buzzwords.</p><h3>Final takeaway</h3><p>We must remind ourselves that the system design does not being with:</p><ul><li>microservices</li><li>sharding</li><li>global scale</li><li>event streaming</li></ul><p>It begins with understanding the basic flow of an application.</p><p>When a user does something:</p><ul><li>who receives the request?</li><li>who validates it?</li><li>where is the data stored?</li><li>who sends the result back?</li><li>what should be trusted?</li><li>what can fail?</li></ul><p>If you can answer those questions clearly, you are on the right track.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a7d67fda2ad5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building a Tiny To-Do App to Understand Client, Server, API, and Database Basics]]></title>
            <link>https://medium.com/@thet.paing.phyo_72486/building-a-tiny-to-do-app-to-understand-client-server-api-and-database-basics-625adbd936a9?source=rss-a84ca2df0b8c------2</link>
            <guid isPermaLink="false">https://medium.com/p/625adbd936a9</guid>
            <dc:creator><![CDATA[Thet Paing Phyo]]></dc:creator>
            <pubDate>Wed, 15 Apr 2026 13:41:50 GMT</pubDate>
            <atom:updated>2026-04-15T13:41:50.646Z</atom:updated>
            <content:encoded><![CDATA[<p>When people hear system design, they often think about massive distributed systems, scaling, and complex architecture diagrams.</p><p>But that is not where you start.</p><p>You start with the most basic question:</p><p><strong>What actually happens when a user does something in an app?</strong></p><p>That is what this lesson is about.</p><p>To make the ideas practical, we are building a tiny to-do app with three parts:</p><ul><li>a <strong>client</strong></li><li>a <strong>server</strong></li><li>a <strong>database</strong></li></ul><p>The app is intentionally small. A user can:</p><ul><li>create a task</li><li>view all tasks</li></ul><p>That is enough to understand the core flow of a backend system.</p><h3>What this lesson teaches</h3><p>By the end of this project, you should understand:</p><ul><li>what the <strong>client </strong>does</li><li>what the <strong>server </strong>does</li><li>what an <strong>API </strong>is</li><li>why we use a <strong>database</strong></li><li>how <strong>request</strong>/<strong>response </strong>works</li><li>the difference between a <strong>read </strong>and a <strong>write</strong></li><li>why the <strong>server must validate input</strong></li></ul><p>The mental model for this lesson is:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DY0ua86Xir-NtupEJp4igw.png" /></figure><h3>The stack</h3><p>To keep this simple, we will use:</p><ul><li><strong>Frontend</strong>: HTML, CSS, JavaScript</li><li><strong>Backend</strong>: Node.js + Express</li><li><strong>Database</strong>: SQLite</li></ul><p>Why this stack?</p><p>Because it is small, local, and easy to understand. The goal is to learn the flow, not fight the tooling.</p><h3>Step 1: Create the project structure</h3><p>Create a project like this:</p><pre>system-design-journey/<br>  client/<br>  server/<br>  README.md</pre><p>Why this structure?</p><p>Because the lesson is about different parts of a system having different jobs.</p><ul><li>client/ is where user interaction happens</li><li>server/ is where requests are handled</li><li>README.md explains what the lesson teaches</li></ul><p>Even the folder structure should reflect the architecture.</p><h3>Step 2: Define the app scope</h3><p>Before writing code, decide what the app actually does.</p><p>For Lesson 1, keep it small:</p><ul><li>create a task</li><li>list all tasks</li></ul><p>That’s it.</p><p>Why so small?</p><p>Because this already gives us:</p><ul><li>one <strong>write </strong>operation: create task</li><li>one <strong>read </strong>operation: get tasks</li></ul><p>That is enough to teach the basic backend flow without adding noise.</p><h3>Step 3: Set up the server</h3><p>Inside the server/ folder, initialize the backend:</p><pre>npm init -y<br>npm install express sqlite3 cors</pre><p>What these packages do:</p><ul><li>express helps us create the server and define routes</li><li>sqlite3 gives us a simple local database</li><li>cors allows the frontend to call the backend in local development</li></ul><p>Now create server/app.js:</p><pre>const express = require(&quot;express&quot;);<br>const cors = require(&quot;cors&quot;);<br>const db = require(&quot;./db&quot;);<br><br>const app = express();<br>const PORT = 3001;<br><br>app.use(cors());<br>app.use(express.json());<br><br>app.get(&quot;/&quot;, (req, res) =&gt; {<br>  res.json({ message: &quot;Server is running&quot; });<br>});<br><br>app.listen(PORT, () =&gt; {<br>  console.log(`Server listening on http://localhost:${PORT}`);<br>});</pre><p>What this does:</p><ul><li>creates a server</li><li>allows JSON request bodies</li><li>enables local frontend/backend communication</li><li>exposes a simple test route</li><li>starts listening for requests</li></ul><p>At this point, the server exists, but it does not do anything useful yet.</p><p>That is okay. Build in small steps.</p><p>Run it with:</p><pre>node app.js</pre><p>If you open http://localhost:3001, you should see:</p><pre>{&quot;message&quot;:&quot;Server is running&quot;}</pre><p>That confirms the backend is alive.</p><h3>Step 4: Add the database</h3><p>Now we add durable storage.</p><p>Why?</p><p>Because server memory is temporary. If the server restarts, anything stored only in memory is lost.</p><p>A database keeps important data around.</p><p>Create server/db.js:</p><pre>const sqlite3 = require(&quot;sqlite3&quot;).verbose();<br>const path = require(&quot;path&quot;);<br><br>const dbPath = path.join(__dirname, &quot;todo.db&quot;);<br><br>const db = new sqlite3.Database(dbPath, (err) =&gt; {<br>  if (err) {<br>    console.error(&quot;Failed to connect to SQLite database:&quot;, err.message);<br>  } else {<br>    console.log(&quot;Connected to SQLite database.&quot;);<br>  }<br>});<br><br>db.serialize(() =&gt; {<br>  db.run(`<br>    CREATE TABLE IF NOT EXISTS tasks (<br>      id INTEGER PRIMARY KEY AUTOINCREMENT,<br>      title TEXT NOT NULL,<br>      created_at TEXT NOT NULL<br>    )<br>  `);<br>});<br><br>module.exports = db;</pre><p>What this does:</p><ul><li>creates or opens a SQLite database file</li><li>creates a tasks table if it does not exist</li><li>stores tasks on disk instead of only in memory</li></ul><p>Our table is simple:</p><ul><li>id identifies the task</li><li>title stores the text</li><li>created_at stores when it was created</li></ul><p>That is enough for Lesson 1.</p><h3>Step 5: Add the read endpoint</h3><p>Now let’s build our first real API route:</p><p>GET /tasks</p><p>This is a <strong>read</strong> operation because it fetches existing data.</p><p>Add this to server/app.js:</p><pre>app.get(&quot;/tasks&quot;, (req, res) =&gt; {<br>  db.all(&quot;SELECT * FROM tasks ORDER BY id DESC&quot;, [], (err, rows) =&gt; {<br>    if (err) {<br>      return res.status(500).json({ error: &quot;Failed to fetch tasks&quot; });<br>    }<br><br>    res.json(rows);<br>  });<br>});</pre><p>What this does:</p><ul><li>receives a request for all tasks</li><li>asks the database for the task rows</li><li>returns them as JSON</li></ul><p>This teaches an important idea:</p><p>The client does not talk directly to the database.<br>The <strong>server</strong> talks to the database and returns the result.</p><h3>Step 6: Add the write endpoint</h3><p>Now let’s build:</p><p>POST /tasks</p><p>This is a <strong>write</strong> operation because it changes the system state.</p><p>Add this to server/app.js:</p><pre>app.post(&quot;/tasks&quot;, (req, res) =&gt; {<br>  const { title } = req.body;<br><br>  if (!title || !title.trim()) {<br>    return res.status(400).json({ error: &quot;Title is required&quot; });<br>  }<br><br>  const trimmedTitle = title.trim();<br>  const createdAt = new Date().toISOString();<br><br>  const query = `<br>    INSERT INTO tasks (title, created_at)<br>    VALUES (?, ?)<br>  `;<br><br>  db.run(query, [trimmedTitle, createdAt], function (err) {<br>    if (err) {<br>      return res.status(500).json({ error: &quot;Failed to create task&quot; });<br>    }<br><br>    res.status(201).json({<br>      id: this.lastID,<br>      title: trimmedTitle,<br>      created_at: createdAt,<br>    });<br>  });<br>});</pre><p>What this teaches:</p><ul><li>the client sends data in the request body</li><li>the server validates the input</li><li>the server writes to the database</li><li>the server returns a response</li></ul><p>The validation part matters a lot.</p><p>Even if the frontend checks for empty input, the server must still validate it.</p><p>Why?</p><p>Because the client is untrusted. A user can bypass the UI and send bad requests directly.</p><p>That is why important validation belongs on the server.</p><h3>Step 7: Test the API first</h3><p>Before building the UI, test the backend by itself.</p><p>This is a good habit. It is easier to debug one layer at a time.</p><p>To test GET /tasks, open:</p><pre>http://localhost:3001/tasks</pre><p>At first, you should get:</p><pre>[]</pre><p>To test POST /tasks, use curl:</p><pre>curl -X POST http://localhost:3001/tasks \<br>  -H &quot;Content-Type: application/json&quot; \<br>  -d &#39;{&quot;title&quot;:&quot;Buy milk&quot;}&#39;</pre><p>You should get a created task back.</p><p>Then visit GET /tasks again and confirm the task is there.</p><p>At this point, you already have:</p><ul><li>a server</li><li>a database</li><li>a read endpoint</li><li>a write endpoint</li><li>server-side validation</li><li>persistence</li></ul><p>That is already the core of Lesson 1.</p><h3>Step 8: Build the client</h3><p>Now we create the part the user interacts with.</p><p>Create client/index.html:</p><pre>&lt;!DOCTYPE html&gt;<br>&lt;html lang=&quot;en&quot;&gt;<br>&lt;head&gt;<br>  &lt;meta charset=&quot;UTF-8&quot; /&gt;<br>  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;<br>  &lt;title&gt;Lesson 1 - Tiny To-Do App&lt;/title&gt;<br>  &lt;link rel=&quot;stylesheet&quot; href=&quot;./style.css&quot; /&gt;<br>&lt;/head&gt;<br>&lt;body&gt;<br>  &lt;main class=&quot;container&quot;&gt;<br>    &lt;h1&gt;Tiny To-Do App&lt;/h1&gt;<br>    &lt;p&gt;Lesson 1: Client, Server, API, and Database Basics&lt;/p&gt;<br><br>    &lt;section class=&quot;task-form&quot;&gt;<br>      &lt;input<br>        type=&quot;text&quot;<br>        id=&quot;taskInput&quot;<br>        placeholder=&quot;Enter a task&quot;<br>      /&gt;<br>      &lt;button id=&quot;addTaskBtn&quot;&gt;Add Task&lt;/button&gt;<br>    &lt;/section&gt;<br><br>    &lt;section&gt;<br>      &lt;h2&gt;Tasks&lt;/h2&gt;<br>      &lt;ul id=&quot;taskList&quot;&gt;&lt;/ul&gt;<br>    &lt;/section&gt;<br>  &lt;/main&gt;<br><br>  &lt;script src=&quot;./script.js&quot;&gt;&lt;/script&gt;<br>&lt;/body&gt;<br>&lt;/html&gt;</pre><p>This gives us:</p><ul><li>an input field</li><li>a button</li><li>a place to render tasks</li></ul><p>That is all the client needs for this lesson.</p><h3>Step 9: Add simple styling</h3><p>Create client/style.css:</p><pre>body {<br>  font-family: Arial, sans-serif;<br>  background: #f7f7f8;<br>  margin: 0;<br>  padding: 0;<br>}<br><br>.container {<br>  max-width: 600px;<br>  margin: 40px auto;<br>  background: white;<br>  padding: 24px;<br>  border-radius: 12px;<br>  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);<br>}<br><br>h1, h2 {<br>  margin-top: 0;<br>}<br><br>.task-form {<br>  display: flex;<br>  gap: 12px;<br>  margin: 20px 0;<br>}<br><br>input {<br>  flex: 1;<br>  padding: 10px;<br>  font-size: 16px;<br>}<br><br>button {<br>  padding: 10px 16px;<br>  font-size: 16px;<br>  cursor: pointer;<br>}<br><br>ul {<br>  padding-left: 20px;<br>}<br><br>li {<br>  margin-bottom: 8px;<br>}</pre><p>This is not about design. It just makes the app readable.</p><h3>Step 10: Fetch and render tasks</h3><p>Now the client needs to call the API and show the results.</p><p>Create client/script.js:</p><pre>const API_BASE_URL = &quot;http://localhost:3001&quot;;<br><br>const taskInput = document.getElementById(&quot;taskInput&quot;);<br>const addTaskBtn = document.getElementById(&quot;addTaskBtn&quot;);<br>const taskList = document.getElementById(&quot;taskList&quot;);<br><br>async function fetchTasks() {<br>  try {<br>    const response = await fetch(`${API_BASE_URL}/tasks`);<br>    const tasks = await response.json();<br>    renderTasks(tasks);<br>  } catch (error) {<br>    console.error(&quot;Failed to fetch tasks:&quot;, error);<br>  }<br>}<br><br>function renderTasks(tasks) {<br>  taskList.innerHTML = &quot;&quot;;<br><br>  for (const task of tasks) {<br>    const li = document.createElement(&quot;li&quot;);<br>    li.textContent = `${task.title} (${task.created_at})`;<br>    taskList.appendChild(li);<br>  }<br>}<br><br>fetchTasks();</pre><p>What this does:</p><ul><li>sends a request to GET /tasks</li><li>receives the task data from the server</li><li>renders it in the browser</li></ul><p>This is the read flow in action:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DY0ua86Xir-NtupEJp4igw.png" /></figure><h3>Step 11: Create tasks from the client</h3><p>Now let the user submit tasks from the UI.</p><p>Add this to client/script.js:</p><pre>async function createTask() {<br>  const title = taskInput.value.trim();<br><br>  if (!title) {<br>    alert(&quot;Please enter a task title.&quot;);<br>    return;<br>  }<br><br>  try {<br>    const response = await fetch(`${API_BASE_URL}/tasks`, {<br>      method: &quot;POST&quot;,<br>      headers: {<br>        &quot;Content-Type&quot;: &quot;application/json&quot;,<br>      },<br>      body: JSON.stringify({ title }),<br>    });<br><br>    if (!response.ok) {<br>      const errorData = await response.json();<br>      throw new Error(errorData.error || &quot;Failed to create task&quot;);<br>    }<br><br>    taskInput.value = &quot;&quot;;<br>    await fetchTasks();<br>  } catch (error) {<br>    console.error(&quot;Failed to create task:&quot;, error);<br>    alert(error.message);<br>  }<br>}<br><br>addTaskBtn.addEventListener(&quot;click&quot;, createTask);<br><br>taskInput.addEventListener(&quot;keydown&quot;, (event) =&gt; {<br>  if (event.key === &quot;Enter&quot;) {<br>    createTask();<br>  }<br>});</pre><p>What this teaches:</p><ul><li>the client reads user input</li><li>the client sends a write request to the server</li><li>the server decides whether the request is valid</li><li>the server stores the task in the database</li><li>the client refreshes the list after success</li></ul><p>Notice something important here:</p><p>The client also checks for empty input, but that is only for better user experience.</p><p>The real protection is still on the server.</p><h3>Step 12: Run the full app</h3><p>Now test the full flow.</p><p>Start the server:</p><pre>cd server<br>node app.js</pre><p>Then open the client in your browser.</p><p>Now try this:</p><ol><li>open the page</li><li>type a task</li><li>click <strong>Add Task</strong></li><li>see it appear in the list</li><li>refresh the page</li><li>confirm the task is still there</li></ol><p>That final refresh is important.</p><p>It shows that the task is not living only in browser state or server memory. It is stored in the database.</p><p>That is one of the biggest lessons here.</p><h3>What this tiny app proves</h3><p>Even though the app is small, it demonstrates the most important Lesson 1 ideas.</p><p><strong>Client</strong></p><p>The client collects input and displays output.</p><p><strong>Server</strong></p><p>The server receives requests, validates input, applies logic, and sends responses.</p><p><strong>API</strong></p><p>The client talks to the server through defined endpoints.</p><p><strong>Database</strong></p><p>The database stores important data durably.</p><p><strong>Request/response</strong></p><p>The entire app works through request/response cycles.</p><p><strong>Read vs write</strong></p><ul><li>GET /tasks is a read</li><li>POST /tasks is a write</li></ul><p><strong>Server-side validation</strong></p><p>The server does not trust the client and validates the title before storing it.</p><h3>The request flow for creating a task</h3><p>Here is the full flow when a user adds a task:</p><pre>User types a task<br>  ↓<br>Client sends POST /tasks<br>  ↓<br>Server validates the title<br>  ↓<br>Server stores the task in SQLite<br>  ↓<br>Server returns the created task<br>  ↓<br>Client refreshes and renders the task list</pre><p>And when the app loads tasks:</p><pre>User opens the app<br>  ↓<br>Client sends GET /tasks<br>  ↓<br>Server reads tasks from SQLite<br>  ↓<br>Server returns JSON<br>  ↓<br>Client renders the task list</pre><p>That is the foundation of many real systems.</p><h3>Why this project is intentionally small</h3><p>It would be easy to add more features:</p><ul><li>login</li><li>delete task</li><li>mark complete</li><li>user accounts</li><li>authentication</li></ul><p>But that would make the lesson worse.</p><p>The goal of Lesson 1 is not to build a full product.</p><p>The goal is to clearly understand the basic architecture of an app:</p><ul><li>where input comes from</li><li>where logic lives</li><li>where data is stored</li><li>how the parts talk to each other</li></ul><p>A small project teaches that better than a bigger messy one.</p><h3>Final takeaway</h3><p>System design does not begin with scaling, sharding, or microservices.</p><p>It begins with understanding a simple flow:</p><p><strong>A user does something. The client sends a request. The server handles it. The database stores or retrieves data. The server responds. The client updates.</strong></p><p>That is what this tiny to-do app shows.</p><p>If you understand this clearly, you already have a real foundation for learning system design.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=625adbd936a9" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[I Thought I Knew JavaScript Until I Had To Explain Why (Part 1)]]></title>
            <link>https://medium.com/@thet.paing.phyo_72486/i-thought-i-knew-javascript-until-i-had-to-explain-why-part-1-2b3ec8cec4e2?source=rss-a84ca2df0b8c------2</link>
            <guid isPermaLink="false">https://medium.com/p/2b3ec8cec4e2</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[javascript-event-loop]]></category>
            <category><![CDATA[mutation]]></category>
            <dc:creator><![CDATA[Thet Paing Phyo]]></dc:creator>
            <pubDate>Mon, 30 Mar 2026 08:34:00 GMT</pubDate>
            <atom:updated>2026-03-30T08:35:03.617Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Understanding JavaScript concepts visually" src="https://cdn-images-1.medium.com/max/1024/1*i4bbxgWIA9PVhzm1n6aF6w.png" /></figure><h3>When I started learning frontend, I could often get things working.</h3><p>I knew some patterns. I knew some syntax. I knew what to type to update an array, attach a click handler, or use a Promise.</p><p>But there was a gap I did not fully respect:</p><p>I often knew <strong>what to use</strong>, but not always <strong>why to use it</strong>.</p><p>That gap matters more than it seems.</p><p>Because in frontend, a lot of bugs do not come from not knowing a feature exists. They come from using the right-looking code with the wrong mental model.</p><p>Over the past phase of learning, I realized that a lot of my JavaScript understanding was like that. I could recognize patterns, but I could not always explain the mechanics precisely. And that is where things break down.</p><h3>Knowing what to use is not the same as knowing why it works</h3><p>It is easy to memorize things like:</p><ul><li>use spread syntax</li><li>use map instead of mutating arrays</li><li>promises run before timeouts</li><li>avoid arrow functions for object methods</li><li>use stopPropagation() for nested clicks</li></ul><p>But memorizing those rules is not the same as understanding them.</p><p>For example, I used to think copying an object with spread syntax meant I had made a safe copy. But that is only true at one level.</p><pre>const next = { ...state };</pre><p>This creates a new outer object. It does <strong>not</strong> automatically create new nested objects and arrays inside it.</p><p>That means this kind of code is still dangerous:</p><pre>const next = { ...state };<br>next.user.address.city = &quot;Chiang Mai&quot;;</pre><p>It looks like I made a copy. But I only copied the outer container. If address was never copied, I am still mutating shared data.</p><p>That was one of the biggest lessons for me: sometimes I was writing “modern JavaScript” without actually reasoning about what was still shared.</p><h3>The real skill is being able to track identity</h3><p>A lot of JavaScript became clearer once I started asking a better question:</p><p><strong>What exactly is new here, and what is still shared?</strong></p><p>That question helped across multiple topics.</p><p>With objects and arrays:</p><ul><li>Did I create a new object?</li><li>Did I create a new array?</li><li>Did I also create new nested objects inside it?</li><li>Or did I just copy the container and keep the inner values shared?</li></ul><p>With functions and closures:</p><ul><li>Is this function reading the current value?</li><li>Or is it closing over a value that was derived earlier?</li></ul><p>With this:</p><ul><li>Am I assuming this belongs to the object?</li><li>Or am I checking how the function is actually being called?</li></ul><p>With the event loop:</p><ul><li>Am I assuming setTimeout(..., 0) means “now”?</li><li>Or do I understand that it still runs later?</li></ul><p>That shift from syntax to mechanics changed a lot for me.</p><h3>JavaScript looks simple until mutation gets involved</h3><p>One of the clearest examples was array methods.</p><p>At a surface level, I already knew methods like map, filter, push, sort, slice, and splice.</p><p>But knowing their names is not enough.</p><p>The real question is: <strong>which ones mutate, and which ones produce new values?</strong></p><p>That difference is huge in frontend.</p><p>For example:</p><pre>const arr = [3, 1, 2];<br>const result = arr.sort();</pre><p>If I only know “sort sorts arrays,” I might miss the important part: sort() mutates the original array and returns the same array.</p><p>So now:</p><ul><li>arr was changed</li><li>result is the same array</li><li>any code depending on immutability can break in confusing ways</li></ul><p>The safer version is:</p><pre>const sorted = [...arr].sort();</pre><p>That tiny difference reflects a much better mental model:<br>make a new array first, then mutate the new one instead of the original.</p><p>That pattern came up again and again. reverse() behaves similarly. push() mutates and returns a length, not a new array. splice() mutates, while slice() copies.</p><p>These are not trivia questions. These are bug sources.</p><h3>“I copied it” is often not enough</h3><p>One of the hardest but most valuable lessons was learning that copying one level is not the same as performing a safe immutable update.</p><p>Consider this:</p><pre>const next = {<br>  ...state,<br>  items: [...state.items]<br>};</pre><p>This creates:</p><ul><li>a new outer object</li><li>a new items array</li></ul><p>But if items contains objects, those inner objects are still shared.</p><p>So this is still a mutation bug:</p><pre>next.items[0].name = &quot;Z&quot;;</pre><p>That line changes the same object that still exists inside the original state.</p><p>This was a turning point for me, because it forced me to stop thinking in broad labels like “copied” or “not copied.”</p><p>Instead, I had to think in paths:</p><p><strong>What exact path am I updating, and did I copy every level along that path?</strong></p><p>That is a much more useful engineering habit.</p><h3>Closures are simple until they become stale</h3><p>Closures were another case where I knew the concept loosely, but not sharply enough.</p><p>I understood the general idea: an inner function can access variables from the outer scope.</p><p>But the more important lesson was this:</p><p>A closure does not magically give you “the latest value” in every form.</p><p>Sometimes you are closing over a variable.<br>Sometimes you are closing over a value that was derived too early.</p><p>That difference matters.</p><p>This version goes stale:</p><pre>function createLogger() {<br>  let count = 0;<br>  const label = `Count is ${count}`;<br><br>  return {<br>    increment() {<br>      count++;<br>    },<br>    log() {<br>      console.log(label);<br>    }<br>  };<br>}</pre><p>Even after incrementing, log() still prints the old label.</p><p>Why? Because label was created once, early, when count was still 0.</p><p>But this version works differently:</p><pre>function createLogger() {<br>  let count = 0;<br><br>  return {<br>    increment() {<br>      count++;<br>    },<br>    log() {<br>      console.log(`Count is ${count}`);<br>    }<br>  };<br>}</pre><p>Now the string is built when log() runs, so it uses the current count.</p><p>This was a useful lesson beyond closures themselves. It taught me to ask:</p><p><strong>Am I reading current state at execution time, or am I using a value that was captured too early?</strong></p><p>That question matters a lot in real frontend code.</p><h3>this is one of those things that punishes vague thinking</h3><p>Before, I had a fuzzy mental model that this somehow referred to the object.</p><p>That works just enough to fool you.</p><p>What I had to learn was:</p><p><strong>this is usually determined by how the function is called, not where it was written.</strong></p><p>That explains why this works:</p><pre>const user = {<br>  name: &quot;Sam&quot;,<br>  sayName() {<br>    console.log(this.name);<br>  }<br>};</pre><pre>user.sayName();</pre><p>But this changes behavior:</p><pre>const fn = user.sayName;<br>fn();</pre><p>Same function. Different call-site.</p><p>And arrow functions made the distinction even clearer: they do not get this from the call-site the same way regular functions do.</p><p>This was another pattern where I had seen the rule before, but understanding the “why” made it stick.</p><h3>The event loop is not hard, but it does require precision</h3><p>I had seen examples with promises and setTimeout, but I had not fully internalized the ordering.</p><p>Now the rule is much clearer in my head:</p><ul><li>synchronous code runs first</li><li>then microtasks</li><li>then macrotasks</li></ul><p>So:</p><pre>console.log(&quot;start&quot;);<br>setTimeout(() =&gt; console.log(&quot;timeout&quot;), 0);<br>Promise.resolve().then(() =&gt; console.log(&quot;promise&quot;));<br>console.log(&quot;end&quot;);</pre><p>prints:</p><pre>start<br>end<br>promise<br>timeout</pre><p>The useful part was not memorizing the output. It was understanding why.</p><p>That helped me reason about nested cases too, like promises created inside timeouts, or timeouts scheduled inside promise callbacks.</p><p>Again, the theme was the same:<br>not just recognizing the pattern, but being able to explain the mechanics.</p><h3>Event bubbling made frontend feel more physical</h3><p>Event propagation also became clearer once I stopped treating it like magic.</p><p>A click is not just “a click handler ran.”<br>It moves through phases.</p><p>That helped me understand bugs like:</p><ul><li>clicking a button inside a clickable card</li><li>a modal backdrop and modal content interfering with each other</li><li>parent handlers running unexpectedly</li></ul><p>stopPropagation() also became more precise in my head. It does not cancel the click itself. It stops the event from continuing through the propagation path.</p><p>That sounds small, but these small wording differences matter because they shape how I debug.</p><h3>The biggest shift: from code recognition to code reasoning</h3><p>If I had to summarize what changed, it is this:</p><p>I am trying to move from recognizing code patterns to reasoning about behavior.</p><p>Before, I might look at code and think:</p><ul><li>that looks right</li><li>I have seen that syntax before</li><li>that is probably the React/JavaScript way to do it</li></ul><p>Now I am trying to ask:</p><ul><li>what is actually being mutated?</li><li>what is newly created?</li><li>what is still shared?</li><li>when is this value being read?</li><li>what queue is this callback entering?</li><li>what determines this here?</li><li>what phase is this event in?</li></ul><p>That feels closer to real engineering.</p><p>Because real bugs usually do not ask whether I have seen the syntax before.<br>They ask whether my mental model is accurate enough to predict behavior.</p><h3>What I learned from this phase</h3><p>The biggest lesson was not one specific JavaScript feature.</p><p>It was that shallow knowledge is enough to write code that looks correct, but not enough to debug confidently.</p><p>Knowing what to use helps you start.</p><p>Knowing why to use it helps you:</p><ul><li>avoid subtle bugs</li><li>debug with less guessing</li><li>reason about state changes</li><li>make safer updates</li><li>explain your code clearly</li><li>trust your own decisions more</li></ul><p>And I think that is the real progression I am after.</p><p>Not just writing code that works once.</p><p>Writing code that I actually understand.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2b3ec8cec4e2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why Developers Struggle With PDFs and How IronPDF Solves the Most Common Pain Points]]></title>
            <link>https://medium.com/@thet.paing.phyo_72486/why-developers-struggle-with-pdfs-and-how-ironpdf-solves-the-most-common-pain-points-47591237fadd?source=rss-a84ca2df0b8c------2</link>
            <guid isPermaLink="false">https://medium.com/p/47591237fadd</guid>
            <category><![CDATA[html-to-pdf]]></category>
            <category><![CDATA[dotnet]]></category>
            <dc:creator><![CDATA[Thet Paing Phyo]]></dc:creator>
            <pubDate>Thu, 11 Dec 2025 09:55:46 GMT</pubDate>
            <atom:updated>2025-12-15T06:49:38.990Z</atom:updated>
            <content:encoded><![CDATA[<p><em>A developer-to-developer perspective from inside Iron Software.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*h-iSZle0hbgzh88ljhrGYg.png" /></figure><p>If you’ve ever built a system that needs to work with PDFs, generating them, merging them, or converting HTML, you’ve probably experienced the moment every developer hits eventually.</p><blockquote>It’s just a PDF. Why is this so complicated?</blockquote><p>I’ve seen that moment from multiple angles: as a support engineer helping developers under tight deadlines, as a web application engineer building the PDF viewer and demo apps, and as someone who has tested countless PDFs that don’t behave the way anyone expects.</p><p>Working at Iron Software has given me a front-row seat to how developers actually interact with PDFs. I’ve seen the assumptions they make, the shortcuts they take, and the surprises they encounter, especially when dealing with complex or inconsistent documents.</p><p>This article is not just about IronPDF as a product. It’s an honest look at the real challenges developers face and the patterns I’ve seen repeatedly while supporting hundreds of users. If you work with PDFs, these insights will feel familiar and hopefully useful.</p><h3>Creating PDFs feels simple — until you need to do it programmatically</h3><p>On the surface, creating a PDF feels easy. Using tools like Adobe Acrobat, Google Docs, Microsoft Word, or even your browser’s “Print to PDF”, the process is instant and predictable. Everything seems effortless.</p><p>This leads many developers to assume:</p><blockquote>If Adobe or Google Docs can generate perfect PDFs with one click, how hard can it be to do this through code?</blockquote><p>But the moment you try to:</p><ul><li>convert dynamic HTML into a PDF</li><li>generate PDFs inside an API</li><li>ensure consistent output across environments</li></ul><p>you quickly discover how much complexity is hidden behind that one-click export.</p><p>Desktop tools hide their entire rendering pipeline behind a simple UI. When you build your own workflow, you are suddenly responsible for:</p><ul><li>rendering engines</li><li>layout engines</li><li>font and asset loading</li><li>timing</li><li>viewport control</li><li>print vs. screen CSS</li></ul><p>That gap between <strong>perceived simplicity</strong> and <strong>actual complexity</strong> is at the core of almost every HTML-to-PDF problem developers run into, and it’s exactly why tools like IronPDF exist.</p><h3>Why HTML-to-PDF is harder than people think</h3><p>HTML was never designed to become a paged, fixed-layout document format. Browsers do their best to interpret layout on the fly, but when converting HTML to PDF, everything becomes stricter.</p><h4>1. HTML relies heavily on dynamic behavior</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yMYCKqArvNxoqdWoXkSIpw.png" /></figure><p>Modern web pages include:</p><ul><li>async JavaScript</li><li>API-driven content</li><li>hydration steps for frameworks</li><li>lazy loading</li><li>animations and transitions</li></ul><p>A PDF engine needs to decide:</p><p><strong>When is the page “ready” to capture?<br></strong>There is no universal answer.</p><h4>2. CSS behaves differently in print mode</h4><p>Developers often forget that:</p><ul><li>print CSS overrides screen CSS</li><li>overflow rules behave differently</li><li>absolute/fixed elements shift in print contexts</li><li>margins and bleed areas change the layout</li></ul><p>Tiny differences in CSS can significantly change the resulting PDF.</p><h4>3. Fonts and assets must be fully loaded</h4><p>A browser can fallback gracefully in real time.<br>A PDF renderer cannot.</p><p>If fonts or images haven’t loaded when rendering begins, the output will differ from what you saw in your browser.</p><h4>4. Frameworks add another layer of complexity</h4><p>React, Vue, Angular, Svelte, and Blazor all hydrate the DOM asynchronously.</p><p>If the PDF engine captures the DOM too early, it will literally snap the loading state.</p><h3>This is where IronPDF’s architecture matters</h3><blockquote>IronPDF uses a Chromium-based rendering engine, which is the same underlying technology used by modern browsers.</blockquote><p>This means IronPDF:</p><ul><li>understands modern CSS</li><li>supports web fonts</li><li>renders JavaScript</li><li>accurately calculates the layout</li><li>behaves like a real browser</li></ul><p>Learn more here: <a href="https://ironpdf.com/tutorials/html-to-pdf/">https://ironpdf.com/tutorials/html-to-pdf/</a></p><p>But IronPDF goes one step further:<br>It gives developers control over <strong>when and how</strong> the page should be rendered for PDF output.</p><pre>var renderer = new ChromePdfRenderer();<br>renderer.RenderingOptions.WaitFor.RenderDelay(1000);<br>renderer.RenderingOptions.WaitFor.JavaScript();<br>renderer.RenderingOptions.WaitFor.NetworkIdle0();<br><br>var pdf = renderer.RenderUrlAsPdf(url);<br>pdf.SaveAs(&quot;output.pdf&quot;);</pre><p>You can control</p><ul><li>render delays</li><li>JavaScript execution</li><li>viewport dimensions</li><li>additional CSS injection</li><li>print settings</li><li>marging and header/footer behavior</li></ul><p>This is essential for real-world web apps, where timing, layout, and loading order are unpredictable.</p><pre>renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Screen;<br>renderer.RenderingOptions.CustomCssUrl = &quot;screen.css&quot;;</pre><h3>The most common HTML-to-PDF issues developers encounter</h3><h4>1. The PDF doesn’t match the webpage</h4><p>Even with a Chromium engine, differences arise due to:</p><ul><li>print vs. screen styling</li><li>font loading delays</li><li>hydration timing</li><li>lazy-loaded elements</li><li>responsive components are resizing differently</li></ul><p><strong>Expected:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bXYunWgzXN9QEQmDiCGTMw.png" /></figure><p><strong>Reality:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dbq2JUApKZtHDxt3FP3z7w.png" /></figure><p>When I build demos, I rely on IronPDF’s configuration options to overcome these issues:</p><ul><li>JavaScript execution hooks</li><li>render-on-event</li><li>custom CSS injection</li><li>controlled viewport settings</li></ul><p>These ensure that the PDF output matches developer expectations.</p><h4>2. Working with form fields and signatures</h4><p>Even though this isn’t as common as HTML-to-PDF, it still surprises developers that:</p><ul><li>form fields are separate from page content</li><li>signatures have both visual and cryptographic layers</li></ul><p>IronPDF handles these details consistently, but for most developers today, <strong>HTML-to-PDF remains the core challenge</strong>.</p><h3>Why IronPDF works so well for developers</h3><p>Across hundreds of developers, one pattern is clear:</p><blockquote>They don’t want to learn PDF internals. They just want a PDF that looks like the webpage.</blockquote><p>IronPDF is built around that expectation: usability.</p><p>The benefits developers appreciate most:</p><h4>1. Browser-accurate HTML-to-PDF</h4><p>The Chromium engine gives developers:</p><ul><li>predictable rendering</li><li>modern CSS support</li><li>accurate framework behavior</li><li>realistic layout calculations</li><li>consistency across environments</li></ul><p>It’s as close to “what you see in a browser is what you get in the PDF” as developers can get, while giving them more control than a regular browser.</p><h4>2. An intuitive C# API</h4><p>Whether you’re:</p><ul><li>rendering HTML strings</li><li>converting live URLs</li><li>applying print styling</li><li>configuring headers and footers</li></ul><p>IronPDF’s API remains consistent, approachable, and easy to integrate. When I build demo apps, the API&#39;s usability is always the standout feature.</p><pre>var renderer = new ChromePdfRenderer();<br>var pdf = renderer.RenderHtmlAsPdf(htmlString);<br>pdf.SaveAs(&quot;output.pdf&quot;);</pre><h4>3. Designed for real developer workflows</h4><p>IronPDF simplifies the entire HTML-to-PDF pipeline by:</p><ul><li>handling Chromium internally</li><li>abstracting timing and loading concerns</li><li>giving developers override controls</li><li>supporting advanced styling scenarios</li></ul><p>This combination of accuracy + usability is exactly what developers want.</p><h3>Final Thoughts</h3><p>If you’re building anything involving PDFs, especially HTML-to-PDF conversion, you’ll eventually run into these challenges. It’s not your fault; it’s simply how HTML, CSS, and browsers behave when turned into a fixed-layout format.</p><p>That’s exactly why IronPDF exists.</p><p>With a Chromium engine and a developer-focused API, IronPDF makes HTML-to-PDF conversion predictable and manageable, so developers can focus on building features instead of wrestling with rendering quirks.</p><p>From support to engineering, I’ve seen firsthand how much time and frustration IronPDF saves developers and how much smoother projects run when HTML-to-PDF “just works”.</p><h3>Try IronPDF</h3><p>If you’re looking for a reliable way to generate or manipulate PDFs in .NET, explore the product here:</p><p><a href="https://ironpdf.com">https://ironpdf.com</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=47591237fadd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Learning Data Structures the Fun Way: Building a Playlist with a Singly Linked List in JavaScript]]></title>
            <link>https://medium.com/@thet.paing.phyo_72486/learning-data-structures-the-fun-way-building-a-playlist-with-a-singly-linked-list-in-javascript-0450cce1c557?source=rss-a84ca2df0b8c------2</link>
            <guid isPermaLink="false">https://medium.com/p/0450cce1c557</guid>
            <category><![CDATA[data-structures]]></category>
            <category><![CDATA[programming-for-beginners]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[learning-to-code]]></category>
            <dc:creator><![CDATA[Thet Paing Phyo]]></dc:creator>
            <pubDate>Tue, 23 Sep 2025 08:49:47 GMT</pubDate>
            <atom:updated>2025-09-23T08:49:47.495Z</atom:updated>
            <content:encoded><![CDATA[<h3>Building a Playlist with a Singly Linked List in JavaScript</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IDJDZJ1mwuHpaS901cKF8A.png" /></figure><h3>Introduction</h3><p>When most people think of data structures, they imagine dry textbook examples with numbers and nodes. But learning them doesn’t have to be boring. I decided to teach myself <strong>singly linked lists</strong> by building something fun and relatable: a <strong>music playlist manager</strong> in JavaScript.</p><p>This small project let me practice creating nodes, linking them together, traversing them, and manipulating the list — all key skills when learning linked lists.</p><h3>Why Linked Lists?</h3><p>Arrays are great, but they have limitations when it comes to <strong>insertions </strong>and <strong>deletions </strong>in the middle of the list. Linked lists solve this by letting each element (or “node”) point to the next one, making reordering fast and memory-efficient.</p><p>Here’s the mental model:</p><pre>Head -&gt; Song 1 -&gt; Song 2 -&gt; Song 3 -&gt; null</pre><p>When you remove Song 2, you simply adjust Song 1’s <strong>next</strong> pointer to skip over it and link to Song 3.</p><h3>The Building Blocks</h3><p>I broke the project into two classes:</p><ol><li>Song — represents each song.</li><li>Playlist — manages the linked list (adding, removing, displaying, playing).</li></ol><p>Here’s what a single node looks like:</p><pre>class Song {<br>  constructor(title, genre, author, singer, duration, next = null) {<br>    this.title = title;<br>    this.genre = genre;<br>    this.author = author;<br>    this.singer = singer;<br>    this.duration = duration;<br>    this.next = next;<br>  }<br>}</pre><h3>Adding Songs to the Playlist</h3><p>The <strong>addSong()</strong> method appends a new node to the end of the list. If the list is empty, the new song becomes the <strong>head</strong>.</p><pre>addSong({ title, genre, author, singer, duration, next }) {<br>  const newSong = new Song(title, genre, author, singer, duration, next);<br><br>  if (!this.head) {<br>    this.head = newSong;<br>    this.current = this.head;<br>  } else {<br>    let last = this.head;<br>    while(last.next) last = last.next;<br>    last.next = newSong;<br>  }<br>}</pre><h3>Removing Songs</h3><p>Deleting a song meant finding the previous node and pointing its <strong>next </strong>to skip over the deleted node — a perfect exercise for linked list traversal.</p><pre>removeSong(titleToDelete) {<br>  if (!this.head) return;<br><br>  if (this.head.title === titleToDelete) {<br>    this.head = this.head.next;<br>    return;<br>  }<br><br>  let prev = this.head;<br>  let curr = this.head.next;<br><br>  while (curr) {<br>    if (curr.title === titleToDelete) {<br>      prev.next = curr.next;<br>      return;<br>    }<br><br>    prev = curr;<br>    curr = curr.next;<br>  }<br>}</pre><h3>Traversing and Playing Songs</h3><p>Finally, I created <strong>playNextSong()</strong> to traverse the list and loop back to the start when reaching the end — just like a real music player.</p><pre>playNextSong() {<br>  if (!this.current) {<br>    console.log(&quot;No songs in the playlist.&quot;); <br>    return;<br>  }<br><br>  console.log(`Now playin: ${this.current.title}`);<br>  this.current = this.current.next || this.head;<br>}</pre><p>There you have it: a clean, simple Linked List project you can build today.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0450cce1c557" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JavaScript Event Delegation: Why It Matters]]></title>
            <link>https://medium.com/@thet.paing.phyo_72486/javascript-event-delegation-why-it-matters-9da772bd6e5e?source=rss-a84ca2df0b8c------2</link>
            <guid isPermaLink="false">https://medium.com/p/9da772bd6e5e</guid>
            <category><![CDATA[event-delegation]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[event-listener]]></category>
            <category><![CDATA[to-do-list]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Thet Paing Phyo]]></dc:creator>
            <pubDate>Sun, 10 Aug 2025 12:40:43 GMT</pubDate>
            <atom:updated>2025-08-10T12:40:43.274Z</atom:updated>
            <content:encoded><![CDATA[<p>When you run JavaScript on a high-end laptop, it’s easy to think performance problems don’t exist. Everything “feels” fast. But that illusion can vanish the moment your app scales or runs on less capable hardware.</p><p>One place where hidden inefficiency can creep in is <strong>event handling</strong>.</p><p>The Problem</p><p>In many interactive UIs, elements are created dynamically:</p><ul><li>New chat messages</li><li>Infinite-scroll product listings</li><li>To-do list tasks</li></ul><p>If you attach event listeners to each element as it’s created, you may:</p><ul><li>Increase memory usage (every listener takes space)</li><li>Increase CPU overhead (more functions for the browser to manage)</li><li>Risk leaking memory if you forget to clean up listeners on removed nodes</li></ul><p>The kicker? On a modern computer, you might not <em>feel</em> these costs until they’re huge, but they’re still there.</p><p>A Concrete Example: Dynamic To-Do List</p><p>Imagine a simple to-do list:</p><ul><li>Add tasks dynamically</li><li>Click a task to toggle “done”</li><li>Delete tasks</li></ul><p>Naive Approach</p><pre>function addTask(text) {<br>  const li = document.createElement(&quot;li&quot;);<br>  li.textContent = text;<br>  <br>  // Attach a click listener<br>  li.addEventListener(&quot;click&quot;, () =&gt; {<br>    li.classList.toggle(&quot;done&quot;);<br>  });<br><br>  document.querySelector(&quot;#taskList&quot;).appendChild(li);<br>}</pre><p>Every new <strong>&lt;li&gt;</strong> gets its own function in memory. Add 1,000 tasks and you now have 1,000 listeners.</p><p>Event Delegation Approach</p><pre>// Attach a click listener<br>document.querySelector(&quot;#taskList&quot;).addEventListener(&quot;click&quot;, (e) =&gt; {<br>  const targeted = e.target.closest(&quot;li&quot;);<br><br>  if (targeted) {<br>    targeted.classList.toggle(&quot;done&quot;);<br>  }<br>});</pre><p>Here we attach one listener to the parent <strong>&lt;ul&gt;</strong> and let events “bubble up”. No matter how many <strong>&lt;li&gt;</strong> we add, the listener count stays constant.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S6nsXdgBO7re3_McgTgUrg.png" /><figcaption>Performance Comparison between Event Delegration and Naive Way</figcaption></figure><p>You can check out the code <a href="https://github.com/thet-paing-phyo-tpp/js-event-delegation">here</a>.</p><p>Takeaways</p><ul><li><strong>Event delegation</strong> reduces memory usage, avoids unnecessary function bindings, and simplifies clean-up.</li><li>It is a cleaner approach.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9da772bd6e5e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Clean and Scalable JavaScript Interop in Blazor Using the Factory Pattern]]></title>
            <link>https://medium.com/@thet.paing.phyo_72486/clean-and-scalable-javascript-interop-in-blazor-using-the-factory-pattern-be6e8212d661?source=rss-a84ca2df0b8c------2</link>
            <guid isPermaLink="false">https://medium.com/p/be6e8212d661</guid>
            <category><![CDATA[interop]]></category>
            <category><![CDATA[blazor]]></category>
            <category><![CDATA[reusable]]></category>
            <category><![CDATA[factory-pattern]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Thet Paing Phyo]]></dc:creator>
            <pubDate>Fri, 08 Aug 2025 02:41:42 GMT</pubDate>
            <atom:updated>2025-08-08T02:41:42.436Z</atom:updated>
            <content:encoded><![CDATA[<p>In Blazor applications, especially when working with multiple JavaScript modules via JS Interop, it’s easy to fall into the trap of repeating the same boilerplate: loading a JS module, invoking functions, and handling disposal. In one of my recent projects, I tackled this repetition by using the Factory Pattern. A classic design pattern that helped me reduce the boilerplate.</p><p>In this article, I’ll walk you through a generalized version of that solution, discuss why it worked, and outline the pros and cons of this approach.</p><p><strong>The Problem</strong><br>Blazor uses <strong><em>IJSRuntime </em></strong>to call JavaScript functions. Typically, we import a module like this:</p><pre>var module = await jsRuntime.InvokeAsync&lt;IJSObjectReference&gt;(<br>  &quot;import&quot;, &quot;./_content/ProjectName/Components/MyComponents.razor.js&quot;<br>);</pre><p>When you have multiple interop classes like FirstJsInterop, SecondJsInterop, etc, each one ends up with:</p><ul><li>The same constructor logic</li><li>The same disposal logic</li><li>Just a different JS file path</li></ul><p>This violates the DRY principle and makes the code harder to maintain and scale.</p><p><strong>The Factory-Based Solution</strong></p><p>Instead of repeating that logic everywhere, I created two reusable layers:</p><ol><li>A Factory Class to Load JS Modules</li></ol><pre>public class InteropFactor<br>{<br>    private readonly IJSRuntime _jsRuntime;<br>    private const string BasePath = &quot;./_content/ProjectName/Components/{0}.razor.js&quot;;<br><br>    public InteropFactory(IJSRuntime jsRuntime) <br>    {<br>        _jsRuntime = jsRuntime;<br>    }<br><br>    public Lazy&lt;Task&lt;IJSObjectReference&gt;&gt; LoadModule(string fileName)<br>    {<br>        string path = string.Format(BasePath, fileName);<br>        return new(() =&gt; _jsRuntime.InvokeAsync&lt;IJSObjectReference&gt;(&quot;import&quot;, path).AsTask());<br>    }<br>}<br></pre><p>This abstracts away the import logic and returns a <strong><em>Lazy&lt;Task&lt;IObjectReference&gt;&gt;</em></strong> to ensure the module loads only once per instance.</p><p>2. A Base Class to Handle Boilerplate</p><pre>public class Interop : IAsyncDisposable<br>{<br>    private readonly Lazy&lt;Task&lt;IJSObjectReference&gt;&gt; _moduleTask;<br><br>    public Interop(Lazy&lt;Task&lt;IJSObjectReference&gt;&gt; moduleTask) <br>    {<br>        _moduleTask= moduleTask;<br>    }<br><br>    public Lazy&lt;Task&lt;IJSObjectReference&gt;&gt; LoadModule(string fileName)<br>    {<br>        string path = string.Format(BasePath, fileName);<br>        return new(() =&gt; _jsRuntime.InvokeAsync&lt;IJSObjectReference&gt;(&quot;import&quot;, path).AsTask());<br>    }<br><br>    protected async ValueTask&lt;T&gt; InvokeAsync&lt;T&gt;(string function, params object[] args)<br>    {<br>        var module = await _moduleTask.Value;<br>        return await module.InvokeAsync&lt;T&gt;(function, args);<br>    }<br><br>    protected async ValueTask InvokeVoidAsync(string function, params object[] args)<br>    {<br>        var module = await _moduleTask.Value;<br>        await module.InvokeVoidAsync(function, args);<br>    }<br><br>    public async ValueTask DisposeAsync()<br>    {<br>        if (_moduleTask.IsValueCreated)<br>        {<br>            var module = await _moduleTask.Value;<br>            await module.DisposeAsync();<br>        }<br>    }<br>}</pre><p>Now, each interop class becomes lean and focused on just its JavaScript responsibilities.</p><p><strong>Example Usage: </strong>MyComponentsJsInterop</p><pre>public class MyComponentsJsInterop : Interop<br>{<br>    public MyComponentsJsInterop(InteropFactory factor)<br>        : base(factory.LoadModule(&quot;MyComponents&quot;)) {} <br>    {<br><br>    }<br><br>    public ValueTask DoFirstTask() =&gt; InvokeVoidAsync(&quot;doFirstTask&quot;);<br>}</pre><p><strong>Summary</strong></p><p>The Factory Pattern helped me create a clean and maintainable approach to managing JS interop in Blazor. If your app grows beyond one or two JS modules, this pattern can save you from boilerplate and complexity.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=be6e8212d661" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Breaking Down QuickSort Like a Baby]]></title>
            <link>https://medium.com/@thet.paing.phyo_72486/breaking-down-quicksort-like-a-baby-5ac5e4c71091?source=rss-a84ca2df0b8c------2</link>
            <guid isPermaLink="false">https://medium.com/p/5ac5e4c71091</guid>
            <category><![CDATA[algorithms]]></category>
            <category><![CDATA[walkthrough]]></category>
            <category><![CDATA[quicksort]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Thet Paing Phyo]]></dc:creator>
            <pubDate>Sat, 28 Jun 2025 21:22:52 GMT</pubDate>
            <atom:updated>2025-06-28T21:22:52.866Z</atom:updated>
            <content:encoded><![CDATA[<p>When I first started learning QuickStort, I thought I got it — until I didn’t.</p><p>I kept hearing terms like pivot, low, high, and swapping, but when I tried tracing it myself using an array like [5, 4, 3, 2, 1], I got confused.</p><ul><li>“Does the pivot change during the sort?”</li><li>“When do I stop?”</li></ul><p>If you’ve ever felt the same, this guide is for you — a step-by-step walkthrough using the array <strong>[5, 4, 3, 2, 1]</strong>.</p><h3><strong>What is QuickSort?</strong></h3><p>QuickSort is a divide and conquer algorithm that:</p><ol><li>Picks a pivot.</li><li>Partitions the array so that values ≤ pivot go to one side and values ≥ pivot go to the other.</li><li>Recursively sorts both sides.</li></ol><p>In this article of QuickSort, we:</p><ul><li>Pick a pivot (we’ll use the middle element)</li><li>Use two pointers: i (from left) and j (from right).</li><li>Move them toward each other and swap when needed.</li></ul><h4>Step 1: Choose a Pivot</h4><p>We’ll pick the pivot from the middle of the array.</p><pre>[5, 4, 3, 2, 1]</pre><p>So the middle element is <strong>3</strong>.<br>That’s our pivot. We’re going to sort everything around it.</p><h4>Step 2: Initialize Pointers</h4><ul><li>Let’s set i to the first index (value = 5)</li><li>Let’s set j to the last index (value = 1)</li></ul><p>So:</p><pre>i = 0 -&gt; arr[i] = 5<br>j = 4 -&gt; arr[j] = 1</pre><h4>Step 3: Compare &amp; Swap</h4><p>Now remember:</p><ul><li>Elements on the left of the pivot should be less than or equal to the pivot.</li><li>Elements on the right of the pivot should be greater than or equal to the pivot.</li></ul><p>So we check:</p><pre>Is arr[i] (5) &gt;= pivot (3)? -&gt; Yes<br>Is arr[j] (1) &lt;= pivot (3)? -&gt; Yes</pre><p>Since both are true, we swap them.</p><p>The array becomes:</p><pre>[1, 4, 3, 2, 5]</pre><p>Now we move the pointers:</p><ul><li>i = 1 now points to 4</li><li>j = 3 now points to 2</li></ul><h4>Step 4: Compare &amp; Swap Again</h4><p>Now check:</p><pre>Is arr[i] (4) &gt;= pivot (3)? -&gt; Yes<br>Is arr[i] (2) &lt;= pivot (3)? -&gt; Yes</pre><p>Both are true again, so we swap:</p><pre>[1, 2, 3, 4, 5]</pre><p>Move the pointers again:</p><ul><li>i = 2, j = 2</li></ul><h4>Step 5: Check If We’re Done</h4><p>At this point: i = 2, j = 2<br>When i ≥ j, we know:</p><ul><li>The pivot is now in the correct place.</li><li>Everything to the left of it is ≤ pivot.</li><li>Everything to the right is ≥ pivot.</li></ul><p>So we return j = 2 to split the array into two subarrays:</p><ul><li>Left: [1, 2]</li><li>Right: [4, 5]</li></ul><p>QuickSort will then recursively sort these parts:</p><p>Even though the array is already sorted at this point, the algorithm doesn’t know that — so it keeps going until each section is just one element.</p><p>Once the recursive calls reach the point where low ≥ high, QuickSort knows it’s done.</p><p>And that’s how it works — one pivot, two pointers, a few swaps, and repeat. Easy as crawling.</p><pre>class Solution {<br>    partition(arr, low, high) {<br>        let mid = Math.floor((low + high) / 2);<br>        let pivot = arr[mid];<br>        let i = low - 1;<br>        let j = high + 1;<br><br>        while (true) {<br>            do {<br>                i++;<br>            } while (arr[i] &lt; pivot);<br><br>            do {<br>                j--;<br>            } while (arr[j] &gt; pivot);<br><br>            if (i &gt;= j) return j;<br><br>            [arr[i], arr[j]] = [arr[j], arr[i]];<br>        }<br>    }<br><br>    quickSort(arr, low, high) {<br>        if (low &lt; high) {<br>            let p = this.partition(arr, low, high);<br>            this.quickSort(arr, low, p);<br>            this.quickSort(arr, p + 1, high);<br>        }<br>    }<br>}<br><br>// Usage<br>const sol = new Solution();<br>let arr = [5, 4, 3, 2, 1];<br>sol.quickSort(arr, 0, arr.length - 1);<br>console.log(arr); // [1, 2, 3, 4, 5]</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5ac5e4c71091" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Faster Approach to Large File Uploads with JavaScript and Controller in Blazor Server]]></title>
            <link>https://medium.com/@thet.paing.phyo_72486/bypass-blazors-inputfile-a-faster-approach-to-large-file-uploads-with-javascript-and-controller-98bf93bd4c5a?source=rss-a84ca2df0b8c------2</link>
            <guid isPermaLink="false">https://medium.com/p/98bf93bd4c5a</guid>
            <category><![CDATA[blazor-server]]></category>
            <category><![CDATA[file-upload]]></category>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[azure-app-service]]></category>
            <category><![CDATA[web-performance]]></category>
            <dc:creator><![CDATA[Thet Paing Phyo]]></dc:creator>
            <pubDate>Fri, 21 Mar 2025 09:45:44 GMT</pubDate>
            <atom:updated>2025-04-04T07:36:43.976Z</atom:updated>
            <content:encoded><![CDATA[<p>I was working on a Blazor Server project, and everything ran smoothly on my local machine. Once I deployed to Azure, file uploads became painfully slow. After digging in, I discovered that Blazor’s built-in <strong>InputFile </strong>component was streaming data through <strong>SignalR </strong>— not ideal.</p><p>In this post, I’ll show you how I bypassed that bottleneck by using plain <strong>JavaScript</strong> and an <strong>ASP.NET Core controller</strong> for a much faster, more reliable upload process.</p><p>Why the Problem Happens:</p><ul><li>In Blazor Server, <strong>InputFile </strong>sends file data via <strong>SignalR </strong>in small chunks, which can be slow or hit limits for larger files.</li><li>You may want a direct HTTP approach (like a standard web form) that doesn’t rely on Blazor’s real-time pipeline.</li><li>Using a controller with <strong>multipart/form-data</strong> is typically faster and more reliable for large files.</li></ul><h4><strong>Step 1: Creating the Controller</strong></h4><p>Here’s a straightforward ASP.NET Core controller that accepts an upload file, writes it to disk, and returns a success message as JSON. This is the core piece for bypassing Blazor’s built-in <strong>InputFile </strong>approach:</p><pre>[Route(&quot;api/[controller]&quot;)]<br>[ApiController]<br>public class UploadFileController : ControllerBase<br>{<br>    [HttpPost(nameof(Upload))]<br>    public async Task&lt;IActionResult&gt; Upload(IFormFile file)<br>    {<br>        // 1. Create a file stream on the server to store the uploaded file.<br>        using var fileStream = new FileStream(file.FileName, FileMode.Create);<br><br>        // 2. Open the uploaded file from the request as a read stream.<br>        using var stream = file.OpenReadStream();<br><br>        // 3. Asynchronously copy all file data into the newly created server file.<br>        await stream.CopyToAsync(fileStream);<br><br>        // 4. Return a JSON response indicating success (and the file path).<br>        return Ok(new <br>        { <br>            FilePath = file.FileName, <br>            Message = &quot;File uploaded successfully!&quot; <br>        });<br>    }<br>}</pre><h4>Step 2: Registering Controllers in Program.cs</h4><p>To ensure your controller (from Step 1) is recognized by ASP.NET Core, you must register the controller services and map the controller routes in your <strong>Program.cs</strong>. For example:</p><pre>// 1. Add controller services<br>builder.Services.AddControllers();<br><br>// 2. Map controller routes<br>app.MapControllers();</pre><h4>Step 3: JavaScript Function to Handle the File Upload</h4><p>Below is a simple JavaScrpit function that listens for a file selection and then immediately uploads that file to our <strong>ASP.NET Core controller</strong> endpoint. You can place this code in a javascript file.</p><pre>export function UploadFile() {<br>    const fileInput = document.getElementById(&quot;file-input&quot;);<br><br>    fileInput.addEventListener(&quot;change&quot;, async (event) =&gt; {<br>        console.time(&quot;timer&quot;);  // Just for performance measurement<br><br>        const file = event.target.files[0];<br>        if (!file) {<br>            console.warn(&quot;No file selected.&quot;);<br>            return;<br>        }<br><br>        // 1. Build a FormData object for the file<br>        const formData = new FormData();<br>        formData.append(&quot;file&quot;, file);<br><br>        // 2. Send the file via fetch to the controller<br>        const response = await fetch(&quot;/api/UploadFile/Upload&quot;, {<br>            method: &quot;POST&quot;,<br>            body: formData<br>        });<br><br>        // 3. Handle the response<br>        if (!response.ok) {<br>            const text = await response.text();<br>        } else {<br>            const data = await response.json();<br>            console.log(data);<br>        }<br><br>        console.timeEnd(&quot;timer&quot;);  // End performance measurement<br>    });<br>}</pre><h4>Step 4: Creating a .NET Method That Calls the JavaScript Function</h4><p>Finally, you can bridge your Blazor Server code and the JavaScript file using <strong>JavaScript Interop</strong>.</p><pre>// This C# method calls the JS function &quot;UploadFile&quot; we defined earlier<br>public async ValueTask UploadFile()<br>{<br>    var module = await _ModuleTask.Value;<br>    await module.InvokeVoidAsync(&quot;UploadFile&quot;);<br>}</pre><p>You must also add the following to the <strong>Program.cs</strong> for dependency injection:</p><pre>builder.Services.AddScoped&lt;AppJsInterop&gt;();</pre><h4>Step 5: Using the Interop in a Blazor Component</h4><p>Finally, here’s an example of how you can call your interop method <strong>UploadFile()</strong> inside a Blazor component. The component renders a plain input file tag and in <strong>OnAfterRenderAsync</strong>, it invokes the JavaScript function to setup the event listener and handle the upload:</p><pre>@page &quot;/using-direct-http&quot;<br><br>@inject AppJsInterop AppJsInterop<br><br>&lt;h3&gt;UsingDirectHttp&lt;/h3&gt;<br><br>&lt;!-- Plain HTML input, no Blazor InputFile component --&gt;<br>&lt;input type=&quot;file&quot; id=&quot;file-input&quot; /&gt;<br><br>@code {<br>    protected override async Task OnAfterRenderAsync(bool firstRender)<br>    {<br>        if (firstRender)<br>        {<br>            // Call the interop method that loads &quot;app.js&quot; <br>            // and invokes the UploadFile function<br>            await AppJsInterop.UploadFile();<br>        }<br><br>        await base.OnAfterRenderAsync(firstRender);<br>    }<br>}</pre><h4>Performance Comparison</h4><p>During testing, I measured how long it took to upload a 7 MB file using Blazor’s <strong>InputFile </strong>and a direct <strong>ASP.NET Core controller</strong> approach. Following are the screenshots comparing the two methods:</p><p><strong>Blazor’s Input File</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/171/1*mPufzk28mw0BjN_0yWnqQA.png" /><figcaption>On Azure</figcaption></figure><p><strong>ASP.NET Core controller</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/286/1*blLGdoOLDerPY9QeBa1MNA.png" /><figcaption>On Azure</figcaption></figure><p>I think the difference is largely due to <strong>SignalR </strong>chunking in Blazor’s <strong>InputFile </strong>approach, whereas direct <strong>HTTP POST</strong> bypasses that overhead. Your exact results may vary depending on file size, network conditions, and hosting environment.</p><p><a href="https://github.com/thet-paing-phyo-tpp/FileUploadTest">Sample Project On GitHub</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=98bf93bd4c5a" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>