<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Durim</title>
    <description>The latest articles on DEV Community by Durim (@durim255).</description>
    <link>https://dev.to/durim255</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4240352%2F469ec07b-d86a-486e-99d4-f52756527151.png</url>
      <title>DEV Community: Durim</title>
      <link>https://dev.to/durim255</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/durim255"/>
    <language>en</language>
    <item>
      <title>How I Built a Python Compiler That Runs in Your Browser, With Real input()</title>
      <dc:creator>Durim</dc:creator>
      <pubDate>Thu, 12 Nov 2026 06:26:37 +0000</pubDate>
      <link>https://dev.to/durim255/how-i-built-a-python-compiler-that-runs-in-your-browser-with-real-input-kj0</link>
      <guid>https://dev.to/durim255/how-i-built-a-python-compiler-that-runs-in-your-browser-with-real-input-kj0</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@durimhoti/how-i-built-a-python-compiler-that-runs-in-your-browser-with-real-input-e59547917bf4" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm Durim, and I build &lt;a href="https://htmlsuite.com" rel="noopener noreferrer"&gt;HTML Suite&lt;/a&gt;, a set of free developer tools that run entirely in the browser: an HTML editor, JSON and YAML formatters, converters and a &lt;a href="https://htmlsuite.com/python-online-compiler" rel="noopener noreferrer"&gt;Python online compiler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Python compiler runs real Python 3.13 on your own device using Pyodide (CPython compiled to WebAssembly). No server executes your code, nothing is uploaded, and you don't need an account. That part was surprisingly easy. The hard part was one tiny built-in function: &lt;code&gt;input()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why input() breaks in the browser
&lt;/h2&gt;

&lt;p&gt;In normal Python, &lt;code&gt;input()&lt;/code&gt; simply pauses the program until you type a line. In the browser, that is a problem. Pyodide runs inside a Web Worker, and everything in JavaScript is asynchronous. A worker can't just stop and wait for the page to send it some text.&lt;/p&gt;

&lt;p&gt;That's why most online Python editors make you type all input into a separate "stdin" box before you press Run. If you forget, or the program asks for one more line than you provided, you get this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EOFError: EOF when reading a line
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My first version did exactly the same, and it was the most common bug people hit. Beginners write &lt;code&gt;name = input("Your name? ")&lt;/code&gt; as one of their very first programs, so it had to work properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: SharedArrayBuffer and Atomics.wait
&lt;/h2&gt;

&lt;p&gt;The trick is to let the worker genuinely block. A &lt;code&gt;SharedArrayBuffer&lt;/code&gt; is memory that both the page and the worker can see. Inside a worker, &lt;code&gt;Atomics.wait()&lt;/code&gt; puts the thread to sleep until another thread changes a value in that shared memory.&lt;/p&gt;

&lt;p&gt;So the flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Python calls &lt;code&gt;input()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The worker tells the page "I need a line" and goes to sleep with &lt;code&gt;Atomics.wait()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The page shows an input field right below the output.&lt;/li&gt;
&lt;li&gt;When you press Enter, the page writes your text into the shared buffer and wakes the worker with &lt;code&gt;Atomics.notify()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The worker reads the line and hands it back to Python, which continues as if nothing happened.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Simplified, the worker side is just this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;readLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Atomics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"input-request"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nc"&gt;Atomics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// sleep until the page answers&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Atomics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;pyodide&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStdin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;readLine&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the Python interpreter never actually stops running, everything is preserved across the pause: variables, random numbers, open loops. You can ask for five inputs in a row and it behaves like a real terminal. UTF-8 works too, so "Grüße" or emoji come through fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch: cross-origin isolation
&lt;/h2&gt;

&lt;p&gt;Browsers only allow &lt;code&gt;SharedArrayBuffer&lt;/code&gt; on pages that are "cross-origin isolated". That means sending two HTTP headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nc"&gt;Cross&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Origin&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Opener&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;same&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;
&lt;span class="nc"&gt;Cross&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Origin&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Embedder&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;credentialless&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These headers are strict: every resource the page loads from another domain has to allow it. Pyodide is loaded from jsDelivr, which already sends the right headers, so that worked. I chose &lt;code&gt;credentialless&lt;/code&gt; instead of &lt;code&gt;require-corp&lt;/code&gt; so that third-party scripts without special headers still load (just without cookies).&lt;/p&gt;

&lt;p&gt;I only set these headers on the Python page and its worker, not on the whole site, so nothing else on HTML Suite is affected.&lt;/p&gt;

&lt;p&gt;One more surprise: the site uses Next.js, and when you click an internal link, Next.js swaps the page without a full reload. That means the new headers never apply, and the page isn't isolated. The fix was to make every link to the Python compiler do a full page load.&lt;/p&gt;

&lt;h2&gt;
  
  
  A fallback for browsers that can't do it
&lt;/h2&gt;

&lt;p&gt;Not every browser supports &lt;code&gt;credentialless&lt;/code&gt;. In that case the page doesn't crash with &lt;code&gt;EOFError&lt;/code&gt;. It shows a clear message and opens the classic stdin box instead, so you can still provide input the old way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;You can try it here: &lt;a href="https://htmlsuite.com/python-online-compiler" rel="noopener noreferrer"&gt;htmlsuite.com/python-online-compiler&lt;/a&gt;. Paste this and press Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"What's your name? "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"How old are you? "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Hi {name}, in 10 years you'll be {age + 10}."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of HTML Suite works the same way: free, in your browser, no signup. There's an HTML editor with live preview, JSON, XML and YAML tools, Word to HTML, Base64, a JWT decoder, a regex tester and color tools.&lt;/p&gt;

&lt;p&gt;If you try it and something doesn't work, or a tool you need is missing, I'd really like to hear about it in the comments.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webassembly</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
