<?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: Pasindu Balasooriya</title>
    <description>The latest articles on DEV Community by Pasindu Balasooriya (@pasindu_balasooriya).</description>
    <link>https://dev.to/pasindu_balasooriya</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%2F3991950%2F9a1e1942-86c8-4fdb-b0e2-7bad50356420.jpg</url>
      <title>DEV Community: Pasindu Balasooriya</title>
      <link>https://dev.to/pasindu_balasooriya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pasindu_balasooriya"/>
    <language>en</language>
    <item>
      <title>I Gave an AI Agent Its Own Identity. Here’s What Actually Happened</title>
      <dc:creator>Pasindu Balasooriya</dc:creator>
      <pubDate>Fri, 11 Sep 2026 13:03:23 +0000</pubDate>
      <link>https://dev.to/pasindu_balasooriya/i-gave-an-ai-agent-its-own-identity-heres-what-actually-happened-2943</link>
      <guid>https://dev.to/pasindu_balasooriya/i-gave-an-ai-agent-its-own-identity-heres-what-actually-happened-2943</guid>
      <description>&lt;p&gt;&lt;em&gt;A hands-on follow-up to&lt;/em&gt; &lt;a href="https://medium.com/@pasindudilshanbalasooriya/a-closer-look-at-how-thunderid-handles-ai-agents-97b82467619c?postPublishedType=initial" rel="noopener noreferrer"&gt;&lt;em&gt;A Closer Look at How ThunderID Handles AI Agents&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. That piece argued, in principle, that delegation beats impersonation. This one tests it against a running server.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs9ujzn2mak1bqa5ailcw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs9ujzn2mak1bqa5ailcw.png" width="799" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The gap between a claim and a server
&lt;/h3&gt;

&lt;p&gt;It is easy to write that an identity system “treats AI agents as first-class identities with delegated, scoped authority.” It is harder to check.&lt;/p&gt;

&lt;p&gt;So I built a small sandbox. One human, one AI agent, one fake API with two endpoints and a rule that should be simple to enforce that &lt;strong&gt;the agent may read the calendar and may not read messages&lt;/strong&gt; , even when it is acting on the human’s behalf.&lt;/p&gt;

&lt;p&gt;Alice, the human can do both. Her scheduling agent should not inherit that. That is the entire experiment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fos1ip9gg4qjpvys8qvse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fos1ip9gg4qjpvys8qvse.png" width="800" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Six phases later I had a working delegation flow, a real RFC 8693 token exchange and an audit trail. I also had a result that was the opposite of what I first thought I had found.&lt;/p&gt;

&lt;p&gt;This post is really about that reversal. It turned out to be the most useful thing that happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1: Reading the box before opening it
&lt;/h3&gt;

&lt;p&gt;Before running anything, I spent twenty minutes just reading the files in the download. That was the best twenty minutes of the project.&lt;/p&gt;

&lt;p&gt;The download is not what I expected. No Docker Compose, no JVM, no installer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7737754kph3l6x6fz0tr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7737754kph3l6x6fz0tr.png" width="798" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two things jumped out of the config and the bootstrap files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agents are a real resource type.&lt;/strong&gt; agent_type sits right next to user_type as an equal and it has a schema shaped like an agent rather than a person.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resource_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agent_type&lt;/span&gt;
&lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;modelProvider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;openai&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;anthropic&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;gemini&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;mistral&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;custom&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;task-automation&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;rag-retrieval&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;code-gen&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                  &lt;span class="nv"&gt;data-analysis&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;orchestrator&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;sub-agent&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                  &lt;span class="nv"&gt;assistant&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;custom&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A user record has a family name and a phone number. An agent record has a model provider and a function. I had set aside a whole phase to fake an agent as a second user account. I could delete it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent is its own OAuth client.&lt;/strong&gt; Agent records carry their own inboundAuthConfig with a client secret. There is no separate application object to register and keep in sync.&lt;/p&gt;

&lt;p&gt;And inside the binary itself all of RFC 8693 was there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;urn:ietf:params:oauth:grant-type:token-exchange
urn:ietf:params:oauth:token-type:id-jag
subject_token actor_token actor_token_type requested_token_type
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My backup plan was to hand-build the token exchange myself. It was already looking unnecessary.&lt;/p&gt;

&lt;p&gt;Then I ran it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;msg="In-process bootstrap completed" imported=26
msg="ThunderID Server started" startup_time=112.6857ms
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;112 milliseconds.&lt;/strong&gt; For a full OAuth2 and OIDC server.&lt;/p&gt;

&lt;p&gt;That number is not showing off. If every environment might want its own separate identity system, then starting one more has to be nearly free. A 57 MB binary that boots in a tenth of a second and keeps its data in SQLite can run per environment, per test, per customer. A traditional IAM stack cannot.&lt;/p&gt;

&lt;h3&gt;
  
  
  The request that settled the project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;$ curl -k https://localhost:8090/.well-known/openid-configuration&lt;/span&gt;

&lt;span class="na"&gt;grant_types_supported&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;client_credentials&lt;/span&gt;
  &lt;span class="s"&gt;authorization_code&lt;/span&gt;
  &lt;span class="s"&gt;refresh_token&lt;/span&gt;
  &lt;span class="s"&gt;urn:ietf:params:oauth:grant-type:token-exchange &amp;lt;-- there it is&lt;/span&gt;
  &lt;span class="s"&gt;urn:openid:params:grant-type:ciba&lt;/span&gt;
  &lt;span class="s"&gt;urn:ietf:params:oauth:grant-type:jwt-bearer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not just compiled in. Actually advertised. My fallback plan was dead, which was the best possible outcome.&lt;/p&gt;

&lt;p&gt;Two more things in that document I had not gone looking for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dpop_signing_alg_values_supported:
  RS256, RS512, PS256, ES256, ES384, ES512, EdDSA,
  ML-DSA-44, ML-DSA-65, ML-DSA-87
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ML-DSA is the post-quantum signature algorithm NIST standardised. All three variants, offered by a running 1.0 server. I had read "post-quantum-safe by design" as marketing.&lt;/p&gt;

&lt;p&gt;It is worth being careful here because this is easy to overstate. Those algorithms are offered for &lt;strong&gt;DPoP proofs and client authentication&lt;/strong&gt;. ID tokens are still signed with RS256 or ES256 and the live key set holds exactly two keys, one RSA and one EC. So token signing is still classical. The quantum-resistant part is proof-of-possession. That is real but it is not "all your tokens are post-quantum now."&lt;/p&gt;

&lt;p&gt;There was also id-jag, a draft standard for carrying agent authority between different identity systems advertised as a supported grant profile in a 1.0 release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 2: Why two identities, not one login
&lt;/h3&gt;

&lt;p&gt;I wrote the permission model down &lt;em&gt;before&lt;/em&gt; creating anything. If you adjust permissions after seeing your results, the experiment is worthless.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiib4x7tc2a31aqlofec4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiib4x7tc2a31aqlofec4.png" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent can do strictly less than the human. So any refusal later should come from that table, not from a bug in my code.&lt;/p&gt;

&lt;p&gt;ThunderID takes its configuration as YAML applied to a running system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resource_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agent&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agent-scheduler&lt;/span&gt;
&lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;modelProvider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;anthropic&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-opus-5&lt;/span&gt;
  &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;assistant&lt;/span&gt;
&lt;span class="na"&gt;inboundAuthConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oauth2&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agent-scheduler&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;REDACTED&amp;gt;&lt;/span&gt;
      &lt;span class="na"&gt;grantTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;client_credentials&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                   &lt;span class="nv"&gt;urn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;ietf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;oauth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;grant-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;token-exchange&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="s"&gt;$ ./thunderid.exe bootstrap -defaults ./resources&lt;/span&gt;
&lt;span class="s"&gt;msg="In-process bootstrap completed" imported=5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That turns “who gave this agent permission to read messages and when?” into a question you answer with git log. There is a change, an author and a date. You are not digging through a database trying to reconstruct history.&lt;/p&gt;

&lt;p&gt;Here is the console before and after. Note that &lt;strong&gt;Agents is a top-level section under IDENTITIES&lt;/strong&gt; , a sibling of Users, Groups and Roles not a checkbox buried in a user profile.&lt;/p&gt;

&lt;p&gt;That turns “who gave this agent permission to read messages, and when?” into a question you answer with git log. There is a change, an author, and a date. You are not digging through a database trying to reconstruct history.&lt;/p&gt;

&lt;p&gt;Here is the console before and after. Note that &lt;strong&gt;Agents is a top-level section under IDENTITIES&lt;/strong&gt; , a sibling of Users, Groups and Roles — not a checkbox buried in a user profile:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F14aeunfk0efd1oozs0op.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F14aeunfk0efd1oozs0op.png" width="799" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmjevee53lftu6h912q4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmjevee53lftu6h912q4t.png" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent has its own ID, its own row and its own lifecycle. And the roles I declared in YAML show up as real objects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faqfmvb15ulcmbl3t7bd5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faqfmvb15ulcmbl3t7bd5.png" width="799" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The separation goes all the way down to storage. CATEGORY is agent, not user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why separation matters
&lt;/h3&gt;

&lt;p&gt;Once the agent has its own record, four things become possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz3ws3d5i6u2wdq9gstok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz3ws3d5i6u2wdq9gstok.png" width="799" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The kill switch is the one that convinces people. If the agent is just a credential saved on Alice’s account, switching it off disrupts Alice. If it is a separate identity, you disable it and she never notices.&lt;/p&gt;

&lt;p&gt;There is a quieter point hiding in that schema too. modelProvider and function are facts you could write policy against. "This agent is an assistant running claude-opus-5" is exactly the sort of thing you might use to decide permissions on a per-request basis. The data model already knows more than the authorization model uses. That turns out to be the theme of this whole project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 3: One door opens, one does not
&lt;/h3&gt;

&lt;p&gt;The API is a small Flask service with two endpoints and one rule each. The only choice that really matters is that it &lt;strong&gt;verifies&lt;/strong&gt; tokens instead of just decoding them.&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;signing_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwks_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_signing_key_from_jwt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;
&lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt&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;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signing_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ES256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;audience&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RESOURCE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# minted for THIS api
&lt;/span&gt;    &lt;span class="n"&gt;issuer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;discovery&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;issuer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# by THIS server
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decoding without verifying works and passes every test you write. It is also worthless as evidence. Anyone can open jwt.io, type {"scope": "calendar.read"} and walk straight in.&lt;/p&gt;

&lt;p&gt;My first token request failed and the failure was more interesting than the success would have been.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"invalid_target"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"error_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"No resource parameter supplied and
                      no default resource server is configured"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ThunderID will not issue a token until it knows &lt;strong&gt;which API the token is for&lt;/strong&gt;. Every token is stamped with its audience and is useless anywhere else. Remember this. It comes back later as half of a strange inconsistency.&lt;/p&gt;

&lt;p&gt;Once I supplied resource the demo worked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;GET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/calendar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;OK&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;GET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/messages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;DENIED&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"insufficient_scope"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required_scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"messages.read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"granted_scopes"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"calendar.read"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same agent. Same script. Same credentials. One door opens and one does not and the only thing that decides it is a signed token.&lt;/p&gt;

&lt;h3&gt;
  
  
  The check that mattered more than the demo
&lt;/h3&gt;

&lt;p&gt;What happens if the agent asks for &lt;strong&gt;more&lt;/strong&gt; than it is allowed?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requested: calendar.read messages.read
granted : calendar.read
HTTP 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No error. The extra scope just did not make it into the token. ThunderID compared what was asked for against what the agent is allowed and kept only the overlap. That is exactly the behaviour I wanted to see.&lt;/p&gt;

&lt;p&gt;Then I tried the odd version of that. Ask only for something it is not allowed to have.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;requested:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;messages.read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;HTTP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"eyJ..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Bearer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"expires_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look carefully. &lt;strong&gt;There is no&lt;/strong&gt;  &lt;strong&gt;scope field at all.&lt;/strong&gt; Not an error, not an empty string. It is simply missing. A valid, properly signed token that grants nothing. (I checked that it really is powerless, 403 at /calendar with granted_scopes: [].)&lt;/p&gt;

&lt;p&gt;That is a sharp edge. If someone writes an API that treats a missing scope claim as "no restrictions," it will let everything through. Mine assumes the opposite.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scopes_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# A missing `scope` means zero scopes, never unrestricted.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scope&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;“200 OK” can mean you have been given nothing. The status code tells you the request was well-formed. Only the scope claim tells you what the token can actually do.&lt;/p&gt;

&lt;p&gt;Watching ThunderID carefully trim an over-broad request here also set a trap for me. It made me assume it would do the same thing during delegation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 4: One token, two parties
&lt;/h3&gt;

&lt;p&gt;This is the phase the whole setup existed for.&lt;/p&gt;

&lt;p&gt;The first obstacle was that there is no password grant, so there was no quick way to log Alice in from a script. I had to follow the real browser flow over HTTP. Authorize, flow engine, credentials, assertion, callback and token. One detail cost me twenty minutes. The flow step needs an action field saying where to go next. Leave it out and you get &lt;strong&gt;HTTP 200&lt;/strong&gt; with flowStatus: INCOMPLETE and the login form again, which looks exactly like a wrong password.&lt;/p&gt;

&lt;p&gt;Then the exchange itself.&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="err"&gt;POST /oauth2/token
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token=&amp;lt;Alice's token&amp;gt;
actor_token=&amp;lt;the agent's token&amp;gt;
requested_token_type=urn:ietf:params:oauth:token-type:access_token
scope=calendar.read
resource=https://localhost:9000/api
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP 200 and the token that came back was the thing I came for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"02900000-...-0002"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"act"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://localhost:8090"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"02900000-...-0003"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"client_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent-scheduler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"calendar.read"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1l8ptfxmwvevirc0l0v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1l8ptfxmwvevirc0l0v.png" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two parties named in one token. The API sees both at once and neither is hidden behind the other. That is the difference between delegation and impersonation written into the data, rather than just described in a design document.&lt;/p&gt;

&lt;p&gt;And it is not only a naming convention. Drop actor_token from the same request and act disappears completely. The token then just says Alice. ThunderID will do either one. Impersonation is available. It simply is not what you get when you ask for delegation.&lt;/p&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhqq4b9rddmihin5k7fdt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhqq4b9rddmihin5k7fdt.png" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Refused at /messages while working for a user who &lt;em&gt;is&lt;/em&gt; allowed to read messages. That was exactly the sentence I wanted to be able to write.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 5: The check I almost didn’t run
&lt;/h3&gt;

&lt;p&gt;Before writing it all up, one thought kept nagging at me. &lt;strong&gt;I had asked for&lt;/strong&gt;  &lt;strong&gt;calendar.read.&lt;/strong&gt; I never checked whether I had to.&lt;/p&gt;

&lt;p&gt;So I ran the same exchange again and asked for something the agent has no right to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffansihw795lrlvypnpu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffansihw795lrlvypnpu8.png" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There it is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1frqrqd93dfdtrfvbwh6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1frqrqd93dfdtrfvbwh6.png" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Leave out scope completely and the agent inherits Alice's &lt;strong&gt;entire&lt;/strong&gt; permission set. Asking for nothing gets you everything.&lt;/p&gt;

&lt;p&gt;So the limit in my first run was not the server holding a line. It was my own script being polite. I had proved that a well-behaved agent stays where it belongs, which is not a security property at all. An agent that has been compromised or tricked the exact thing delegation is supposed to protect you from just asks for more and gets it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What makes this a finding
&lt;/h3&gt;

&lt;p&gt;ThunderID does enforce this limit elsewhere. Same agent, same server and same scope requested.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2stzq0x9fwldoi7vdy4c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2stzq0x9fwldoi7vdy4c.png" width="764" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The logic exists. It runs when the agent acts as itself. It does not run when the agent acts for a user. That is the one case where the agent reaches past its own identity and so it is the one case where you would most want the check.&lt;/p&gt;

&lt;p&gt;To be fair, &lt;strong&gt;RFC 8693 leaves the resulting scope up to the authorization server&lt;/strong&gt; and does not require narrowing it to the actor’s rights. This is a permissive default. But it does mean the carefully separated agent identity its own record, its own credentials, its own roles is quietly stepped around at the exact moment authority gets handed over.&lt;/p&gt;

&lt;p&gt;Token exchange was standardised before AI agents were the reason anyone cared about it. Its original job was moving trust between services, where the actor is usually a trusted gateway. Agents flip that around. The actor is now the &lt;strong&gt;least&lt;/strong&gt; trusted thing in the exchange. Its permissions are exactly what should limit the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  One thing that does bound it
&lt;/h3&gt;

&lt;p&gt;To be accurate about what is and is not enforced: the &lt;strong&gt;subject token’s&lt;/strong&gt; scopes are a real limit. Same agent, same request, and only Alice’s token changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7hk8dqt15ipnmnymzi2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7hk8dqt15ipnmnymzi2.png" width="799" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So “what was requested, narrowed by the subject’s scopes” is enforced. It is specifically the agent’s own scopes that never enter the calculation.&lt;/p&gt;

&lt;p&gt;That leaves you a workaround. Issue user tokens with narrow scopes and an agent cannot pull more out of an exchange. But it is a workaround, not the property the docs describe. Every part of your system that logs a user in has to scope its tokens down correctly and keep doing it forever. And it still never looks at the agent’s permissions. Two agents with completely different access rights, exchanging the same user token, get identical results.&lt;/p&gt;

&lt;h3&gt;
  
  
  The audit log said it first
&lt;/h3&gt;

&lt;p&gt;I built the audit trail expecting it to be a box-ticking exercise. It described the problem more clearly than anything on my screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8gq7xgoktigz7066hi5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8gq7xgoktigz7066hi5.png" width="800" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same agent, same endpoint, opposite outcomes. The only visible difference is the FOR USER column. &lt;strong&gt;On its own the agent is contained. Working for a human, it is not.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That second row is only possible to write because the token named two parties. With impersonation every row would say “alice,” and “which agent did this and under what permission?” would be unanswerable. Not difficult but unanswerable because nobody ever recorded it.&lt;/p&gt;

&lt;p&gt;That is the practical case for delegation over impersonation, and I find it more convincing than the theoretical one. It is not that impersonation is philosophically wrong. It is that it throws away the evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 6: The dive in to code
&lt;/h3&gt;

&lt;p&gt;I checked what ThunderID’s own documentation says is supposed to happen.&lt;/p&gt;

&lt;p&gt;The token exchange reference (v1.0.1, guides/protocols/oauth-oidc/token-exchange.mdx):&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Final scopes must also be permissions on the target resource server and&lt;/em&gt; &lt;strong&gt;&lt;em&gt;authorized for the issuing app or agent&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It says plainly that the agent’s own permissions limit what it gets.&lt;/p&gt;

&lt;p&gt;So one of us was wrong. ThunderID is open source, so I went and looked.&lt;/p&gt;

&lt;p&gt;backend/internal/oauth/oauth2/granthandlers/token_exchange.go&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tokenExchangeGrantHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;getScopes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tokenRequest&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;subjectScopes&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// &amp;lt;-- only the SUBJECT's scopes arrive here&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrorResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tokenRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scope&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&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;subjectScopes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="c"&gt;// no scope requested -&amp;gt; ALL of the subject's scopes&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// ...filter requested scopes to those present in the subject token&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What actually happens is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requested ∩ subject-token scopes ∩ target resource server scopes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things. The fourth one the docs promise, the agent’s own permissions, is not there.&lt;/p&gt;

&lt;p&gt;The product documents a security property and does not implement it. Someone building an API after reading either page would reasonably believe an agent limited to calendar.read cannot come back holding messages.read. They would be wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  So is it just ThunderID?
&lt;/h3&gt;

&lt;p&gt;No. And this is where it gets interesting.&lt;/p&gt;

&lt;p&gt;I had WSO2 Identity Server 7.3.0 installed, a mature IAM platform with fifteen years behind it. I rebuilt the same setup there. alice with three scopes, an agent with only calendar.read and token exchange switched on.&lt;/p&gt;

&lt;p&gt;Two things were different and one was not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IS limits the agent acting as itself&lt;/strong&gt; , same as ThunderID. A tie.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IS refuses delegation by default.&lt;/strong&gt; Where ThunderID hands a delegated token to any actor that can authenticate, IS said no.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Impersonation request rejected — impersonator: agentsched, subject: alice
Error: Authenticated user doesn't have impersonation permission for client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IS requires the actor to hold internal_user_impersonate and it &lt;strong&gt;creates the&lt;/strong&gt;  &lt;strong&gt;may_act binding itself&lt;/strong&gt; after checking the actor is allowed. ThunderID accepts an actor_token the caller supplies and checks nothing. Deny by default versus allow by default. &lt;em&gt;IS grants the delegation authority. ThunderID takes your word for it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But once it is allowed, IS gives out exactly what ThunderID gives out.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0qdgvudc44cuxso66oq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0qdgvudc44cuxso66oq1.png" width="799" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An agent holding one scope got every scope its user holds. Same behaviour, different product.&lt;/p&gt;

&lt;p&gt;So the difference is the &lt;strong&gt;gate&lt;/strong&gt; , not the  &lt;strong&gt;limit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsx5aiud0lbdwfxavkpy7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsx5aiud0lbdwfxavkpy7.png" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two separate implementations of the same standard and neither one limits delegated scope by the actor’s own rights. One of them says in writing that it does.&lt;/p&gt;

&lt;p&gt;That is a far better finding than “a product has a bug.” It says the limit is &lt;strong&gt;understood to be a good idea&lt;/strong&gt; someone wrote it into ThunderID’s documentation, and is still &lt;strong&gt;missing from both products&lt;/strong&gt;. The intention is there. The mechanism is not.&lt;/p&gt;

&lt;h3&gt;
  
  
  A debugging note worth sparing you
&lt;/h3&gt;

&lt;p&gt;Getting IS to finish this took much longer than it should have, for two reasons.&lt;/p&gt;

&lt;p&gt;Leaving out actor_token gives you this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"invalid_request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"error_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Invalid Subject Token. Subject token is not ACTIVE."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The subject token is fine. The &lt;em&gt;actor&lt;/em&gt; token is the one that is missing. I spent a long time chasing token persistence settings, including a server restart, because the error pointed at the wrong parameter. The answer is in the docs, use response_type=id_token subject_token and the id_token you get back is the actor token you send in.&lt;/p&gt;

&lt;p&gt;Second, internal_user_impersonate is a tenant-level permission. Add it to an &lt;strong&gt;application&lt;/strong&gt; -level role and you get 200, it shows up on the role and it survives a restart but it never actually takes effect. It has to be an &lt;strong&gt;organization&lt;/strong&gt; -level role. Silently accepting a permission that does nothing cost me four failed attempts.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I would tell you to take away
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Agent identity in ThunderID is real.&lt;/strong&gt; I came in sceptical and it held up completely. Separate entity type, agent-shaped schema, its own credentials, its own roles, configuration as code and a server that starts in a tenth of a second. None of it is a service account with a new label.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traceability and containment are different things and only one of them is delivered.&lt;/strong&gt; The act claim tells you who acted. It does not stop them. An agent limited to calendar.read can reach everything its user can as soon as it acts for that user, and the audit log will faithfully record it doing so. &lt;strong&gt;Both products I tested behave this way&lt;/strong&gt; and one of them documents the opposite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch the asymmetry.&lt;/strong&gt; The same server refuses to issue a token without a resource parameter. Narrowing by audience is required and fails safe. Narrowing by the actor's permissions does not happen at all and fails open. Two decisions about narrowing, opposite defaults, one product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And the one about method, which is why this post exists.&lt;/strong&gt; My first run succeeded. 403 at /messages, exactly the result I was hoping for, from a real server with a real transcript. It was only true because I had politely asked for less than I could have had.&lt;/p&gt;

&lt;p&gt;A passing test and a broken security property produced identical output.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;When a test passes because you asked nicely, you have tested your own politeness.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is a second version of the same lesson. It cost me nothing and nearly cost me the finding. C &lt;strong&gt;heck the documentation against the behaviour, then check the source against both.&lt;/strong&gt; The docs alone would have convinced me the limit was there. The measurement alone looked like a defensible default. Only putting all three side by side showed what was really going on.&lt;/p&gt;

&lt;p&gt;The question is never “did the restriction hold?” It is “what happens when something tries to break it?” I nearly skipped that check. It is now the only thing in this project I would stake anything on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Honest limitations
&lt;/h3&gt;

&lt;p&gt;This is a local test sandbox, not a deployment. Self-signed certificates, verification switched off, two identities, a fake API with two endpoints and no attempt at load, multi-tenancy or federation.&lt;/p&gt;

&lt;p&gt;ThunderID is a new project, and its docs may lag its code. Several things here I could only find by reading the binary and the shipped YAML. Both results are &lt;strong&gt;default behaviour&lt;/strong&gt;. Whether some policy setting can add the missing limit in either product is a question I have not answered.&lt;/p&gt;

&lt;p&gt;None of that makes the direction less interesting. Agent identity is going to matter and ThunderID is one of the earliest serious attempts to design for it directly instead of bolting it onto a system built for people.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Code and resources at:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pasindubalasooriya/thunderid-agent-sandbox" rel="noopener noreferrer"&gt;GitHub - pasindubalasooriya/thunderid-agent-sandbox: Local sandbox showing scoped, delegated access for an AI agent with WSO2 ThunderID: RFC 8693 token exchange, the act claim, and a resource server that enforces scope.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wso2</category>
      <category>ami</category>
      <category>wso2is</category>
      <category>thunderid</category>
    </item>
    <item>
      <title>A Closer Look at How ThunderID Handles AI Agents</title>
      <dc:creator>Pasindu Balasooriya</dc:creator>
      <pubDate>Thu, 10 Sep 2026 16:38:10 +0000</pubDate>
      <link>https://dev.to/pasindu_balasooriya/a-closer-look-at-how-thunderid-handles-ai-agents-2jh4</link>
      <guid>https://dev.to/pasindu_balasooriya/a-closer-look-at-how-thunderid-handles-ai-agents-2jh4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A_i6jRnjoZrLo4nAY" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A_i6jRnjoZrLo4nAY" width="1024" height="287"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;thunder-id/thunderid: ThunderID is a high-performance, open-source identity stack designed for developers to secure and manage access for humans, AI agents, and machines through fully composable identity flows.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In the earlier article, we touched briefly on the idea that ThunderID treats AI agents as first class identities. This article slows down and looks at that idea in much more detail. What does it actually mean for an AI agent to “have an identity”? How does permission work when there is no human physically typing a password? And what happens when something goes wrong?&lt;/p&gt;

&lt;p&gt;This is written for someone who already understands the basics of logging in with a username and password, but has not yet thought deeply about what happens when the thing logging in is a piece of software acting on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why AI Agents Need Their Own Identity System
&lt;/h3&gt;

&lt;p&gt;For most of the history of computing, identity systems were built around one simple assumption. Ahuman sits down, proves who they are and then uses an application. The application is just a window the human looks through.&lt;/p&gt;

&lt;p&gt;AI agents break that assumption. An agent might check your calendar every morning without you touching a keyboard. It might book a flight, reply to an email or move money between accounts, all while you are asleep. The agent is not just a window anymore. It is acting.&lt;/p&gt;

&lt;p&gt;This creates a real problem. If the agent uses your saved password or your personal login token to do all of this, then from the system’s point of view, there is no difference between you and the agent. If the agent makes a mistake, or is tricked into doing something harmful, there is no way to separate “the user did this” from “the agent did this while pretending to be the user.”&lt;/p&gt;

&lt;p&gt;ThunderID was built specifically to close this gap.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Old Assumption That Broke
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0a6lipzx4kglquv4ikhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0a6lipzx4kglquv4ikhi.png" width="799" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the old world, identity systems only had to answer one question. &lt;strong&gt;Is this really you?&lt;/strong&gt; In the new world, they have to answer a second question just as often. &lt;strong&gt;Which agent is acting right now and what exactly did you allow it to do?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Four Pillars
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw5xu1pu5fiu4vlju4poj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw5xu1pu5fiu4vlju4poj.png" width="768" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first pillar carries most of the weight for our purposes, so we will spend the most time there. The other three each solve a problem that only becomes obvious once agents are involved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pillar 1: Agent-Native Identity
&lt;/h3&gt;

&lt;p&gt;This is the foundation. It has four parts worth separating, because people often collapse them together and then wonder why the result feels unsafe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every agent gets its own record
&lt;/h3&gt;

&lt;p&gt;The starting point is simple but important. In ThunderID, an AI agent is not a hidden feature of a human’s account. It is its own identity, stored and managed separately, the same way a human user account is stored and managed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fph099l4ipg7irziungys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fph099l4ipg7irziungys.png" width="799" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This might sound small but it changes everything downstream. Once an agent has its own record, the system can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give the agent its own permissions, separate from the human’s full permissions&lt;/li&gt;
&lt;li&gt;Track the agent’s actions on their own, without mixing them into the human’s history&lt;/li&gt;
&lt;li&gt;Turn the agent off completely, without touching the human’s account at all&lt;/li&gt;
&lt;li&gt;Apply different security rules to agents than to humans, since agents behave differently (they can act constantly, at machine speed, without ever getting tired)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what &lt;strong&gt;“first class”&lt;/strong&gt; means in practice. The agent is a real, trackable entity with its own identifier, owner, credentials, attributes and lifecycle, not an invisible extension of a person.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delegated authority, not impersonation
&lt;/h3&gt;

&lt;p&gt;This is probably the single most important idea in the whole system, so it is worth explaining carefully.&lt;/p&gt;

&lt;p&gt;When you let an agent act for you, there are two very different ways this can happen underneath the surface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Impersonation.&lt;/strong&gt; The agent is given something that makes it indistinguishable from you. It uses your login, your token or a copy of your identity. From the system’s point of view, “the agent” and “you” are the exact same thing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmj95ylwdbr7vve71rp40.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmj95ylwdbr7vve71rp40.png" width="796" height="84"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The risk here is obvious. If the agent does something wrong, damaging or simply unexpected, it is very hard to separate that from something you did yourself. This also means the agent silently has access to everything you have access to, not just what it needs for its task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delegation.&lt;/strong&gt; The agent keeps its own separate identity, but is granted specific, limited permission to act for you. The system always keeps a clear line between “you” and “the agent acting for you.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdyu78mdyqbhacmhb2bi1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdyu78mdyqbhacmhb2bi1.png" width="800" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ThunderID is built around delegation, not impersonation. This is the core design decision that makes everything else, permissions, tracking and safety, possible.&lt;/p&gt;

&lt;p&gt;It is worth seeing how this looks in practice, because it is more concrete than it sounds. When an agent acts for a user, the access token it carries names both parties. The user appears in the standard subject field and the agent appears in a separate actor field. A service receiving that token can read both at once. It knows which user authorized the action and which agent actually carried it out, without either one being hidden behind the other. That single detail is what separates delegation from impersonation in the actual data, rather than just in intent.&lt;/p&gt;

&lt;p&gt;ThunderID gives an agent three distinct ways to act and they are deliberately kept separate. On its own behalf, on behalf of a signed-in user or delegated to it by another agent. An agent starts out able to act only as itself, and acting for a user is something you switch on explicitly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters in practice.&lt;/strong&gt; Imagine you allow an agent to check your calendar and suggest meeting times. With impersonation, that agent technically also has the power to read your private messages, delete files, or make payments, because it is “you.” With delegation, the agent only has the calendar permission you actually granted it. Even if the agent is compromised or behaves unexpectedly, the damage is limited to what it was actually allowed to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consent aware access
&lt;/h3&gt;

&lt;p&gt;Delegation only works if permission is actually given on purpose, not assumed.&lt;/p&gt;

&lt;p&gt;Think about how a mobile phone asks “Allow this app to use your camera? Yes or No” the first time an app tries to use it. ThunderID applies the same basic idea to AI agents, but in a more structured way, since an agent might need many different types of access over time, not just one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdn2m9dy0fma3rvto8jcn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdn2m9dy0fma3rvto8jcn.png" width="800" height="851"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important word here is “specifically.” Broad, one time consent (“yes, this agent can do anything forever”) defeats the purpose. Consent aware access is meant to work at the level of individual permissions, so a human can say yes to “check my calendar” while still saying no to “read my private messages,” even if both requests come from the same agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traceability
&lt;/h3&gt;

&lt;p&gt;Even with careful delegation and consent, things can still go wrong. A permission might be misused, a bug might cause unexpected behavior or an agent might be tricked by malicious input from somewhere else. When that happens, the question becomes what actually happened and who is responsible?&lt;/p&gt;

&lt;p&gt;Because the agent has its own identity and the token names both parties, the system can record&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which agent performed the action&lt;/li&gt;
&lt;li&gt;Which human it was acting on behalf of&lt;/li&gt;
&lt;li&gt;What permission it used to do it&lt;/li&gt;
&lt;li&gt;What it actually did&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F07jula5sb1ozvgts0t2e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F07jula5sb1ozvgts0t2e.png" width="800" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That last row matters just as much as the successful ones. A record of a denied action, an agent trying to do something it was not allowed to do is often the earliest warning sign that something needs attention.&lt;/p&gt;

&lt;p&gt;This audit trail is what turns “we hope the agent behaved correctly” into “we can prove, exactly what the agent did and did not do.” Note that it is only possible because of the first three parts. Without a separate identity, there is no agent to name. Without delegation, there is no second party to record. Without consent, there is no specific permission to point at.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pillar 2: Post-Quantum-Safe by Design
&lt;/h3&gt;

&lt;p&gt;This is a more technical pillar, but it has a direct consequence for agents.&lt;/p&gt;

&lt;p&gt;Future, much more powerful computers, called quantum computers, may eventually be able to break some of the encryption methods used today. For an ordinary login system this is a slow-moving concern, because a stolen password can simply be changed.&lt;/p&gt;

&lt;p&gt;Credentials issued to agents are different. They may need to stay valid and trustworthy for a long time and other systems may be making decisions based on the assumption that such a credential cannot be forged. A signature that becomes forgeable in ten years is a problem for anything signed today that is still expected to be trusted then.&lt;/p&gt;

&lt;p&gt;ThunderID is built with what is called a &lt;strong&gt;crypto agile foundation&lt;/strong&gt; , meaning the signing and encryption algorithms it uses are configurable rather than baked in. This is not a future plan. ThunderID already supports ML-DSA, one of the post-quantum signature algorithms standardized by NIST, for signing the tokens it issues alongside the classical RSA and ECDSA algorithms in use today.&lt;/p&gt;

&lt;p&gt;In simple terms the security promises can keep holding up even as computing power changes and the change can be made by configuration rather than by rebuilding the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pillar 3: Decentralized Identity
&lt;/h3&gt;

&lt;p&gt;The pillars so far describe trust inside one system. This one is about trust between systems that do not share a database.&lt;/p&gt;

&lt;p&gt;Imagine an AI agent built by your company needs to talk to a completely different company’s service. How does that other service know the agent is legitimate and not some random piece of software pretending to be your agent?&lt;/p&gt;

&lt;p&gt;The traditional answer is to call back and check every single time, which is slow and creates a lot of extra traffic between systems. ThunderID supports a different approach, based on something called a &lt;strong&gt;verifiable credential&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a verifiable credential actually is
&lt;/h3&gt;

&lt;p&gt;A simple way to think about it is a digital, tamper proof certificate. Instead of a service having to ask “is this real?” every time, the agent can simply present a credential that was already signed by a trusted issuer. Anyone who receives it can check the signature and know immediately that it is genuine, without contacting the original issuer again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fotfvbh6077o37r1a4wfw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fotfvbh6077o37r1a4wfw.png" width="800" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This pattern has a name in the identity world. &lt;strong&gt;The issuer, holder, verifier model.&lt;/strong&gt; The agent is the holder, storing the credential in something like a digital wallet. ThunderID can play either of the other two roles. It issues credentials and it can also act as the verifier that checks a credential presented to it. That second half matters because a service using ThunderID does not have to implement any credential protocol itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  The two standards behind this
&lt;/h3&gt;

&lt;p&gt;ThunderID supports two emerging open standards for this process&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenID4VCI (OpenID for Verifiable Credential Issuance):&lt;/strong&gt; the process of ThunderID creating and handing a credential to an agent’s wallet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenID4VP&lt;/strong&gt; &lt;strong&gt;(OpenID for Verifiable Presentations):&lt;/strong&gt; the process of an agent showing that credential to another service and that service checking it is valid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important idea is simply this. Credentials let trust travel with the agent, instead of requiring a phone call back to headquarters every time trust needs to be established.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thinking beyond one central system
&lt;/h3&gt;

&lt;p&gt;Verifiable credentials connect to a bigger idea. In a traditional identity system, one central authority is the only source of truth and everyone has to check with it directly. Decentralized identity spreads that trust using ideas like&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decentralized identifiers:&lt;/strong&gt; a way of identifying something (a person, an agent, an organization) that is not tied to any single company’s database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Digital wallets:&lt;/strong&gt; a place, controlled by the agent or the person, where credentials are stored and reused across many different services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust registries:&lt;/strong&gt; lists that say which issuers are actually trustworthy, so a verifier can check not just “is this credential real” but also “was it issued by someone I should actually trust.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters more and more as AI agents start working across company boundaries, not just inside one organization’s walls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pillar 4: Lightweight Runtime with GitOps Support
&lt;/h3&gt;

&lt;p&gt;The last pillar sounds like an operations concern rather than an identity one. For agents, it turns out to be both.&lt;/p&gt;

&lt;p&gt;ThunderID is written in Go and runs as a small, headless, API-first service. Headless means the console interface, the SDKs and the end-user login screens all sit on top of the API rather than being welded into the core, so they can be replaced.&lt;/p&gt;

&lt;p&gt;The GitOps half is the part that matters for agents. Identity configuration in ThunderID is declarative. Agents, applications, organization units, flows and users can all be defined as YAML files and applied to a running system. That makes identity configuration reviewable the same way application code is.&lt;/p&gt;

&lt;p&gt;Consider what this means when the thing being configured is an agent&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9emnz8j54238a24ahg1a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9emnz8j54238a24ahg1a.png" width="800" height="836"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An agent’s permissions are exactly the kind of thing you want a record of. Answering “who gave this agent the ability to spend money and when?” becomes a version control question rather than an archaeology exercise.&lt;/p&gt;

&lt;p&gt;There is a second connection worth noting. Agents in ThunderID are not only identities the system manages. They are also actors that can interact with the system itself. Core identity operations are exposed through standard APIs and through MCP, the protocol AI tools increasingly use to call external capabilities. An agent can therefore discover and invoke identity functions programmatically, with a human in the loop. The same headless, API-first design that makes GitOps possible is what makes this possible too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting It All Together: A Worked Example
&lt;/h3&gt;

&lt;p&gt;Let us walk through a realistic scenario using everything above.&lt;/p&gt;

&lt;p&gt;Imagine you have a personal AI assistant that manages your work travel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fofbsvco217moqwls40u0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fofbsvco217moqwls40u0.png" width="800" height="1598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;None of these ideas work well alone. Together they let an AI agent act with real independence, while still staying safe, limited and accountable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where ThunderID Fits in the Bigger Picture
&lt;/h3&gt;

&lt;p&gt;ThunderID is not meant to exist alone. It is designed to drop into the infrastructure a company already runs, which is why it ships deployment paths for Docker, Kubernetes and OpenChoreo, WSO2’s open source internal developer platform, where it can be provisioned declaratively as a platform resource.&lt;/p&gt;

&lt;p&gt;It is also not trying to replace WSO2 Identity Server for traditional human login. It is solving a problem those older systems were never originally designed for.&lt;/p&gt;

&lt;p&gt;The core shift ThunderID represents is a change in what “identity” even needs to mean. It is no longer enough to ask “is this really you?” Increasingly, systems also need to ask “which agent is acting, on whose behalf, with what permission and can it prove that?”&lt;/p&gt;

&lt;p&gt;The four pillars are how it answers. Agent-native identity gives every agent its own record, favors delegation over impersonation, requires specific consent and records what happened. Post-quantum-safe design keeps those records trustworthy over time. Decentralized identity lets an agent carry proof of itself beyond the system that issued it. And a lightweight, GitOps-friendly runtime keeps the whole arrangement reviewable rather than improvised.&lt;/p&gt;

&lt;p&gt;Taken together, they form one of the clearer early blueprints for how AI agents can operate with real autonomy without operating in the dark.&lt;/p&gt;

</description>
      <category>wso2</category>
      <category>thunderid</category>
      <category>ami</category>
    </item>
    <item>
      <title>Why WSO2 Identity Server, WSO2 Identity Platform and ThunderID All Exist</title>
      <dc:creator>Pasindu Balasooriya</dc:creator>
      <pubDate>Wed, 09 Sep 2026 04:11:04 +0000</pubDate>
      <link>https://dev.to/pasindu_balasooriya/why-wso2-identity-server-wso2-identity-platform-and-thunderid-all-exist-26lh</link>
      <guid>https://dev.to/pasindu_balasooriya/why-wso2-identity-server-wso2-identity-platform-and-thunderid-all-exist-26lh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Identity management sounds simple on the surface. A user logs in, the system checks who they are and access is granted. But underneath that simple idea sits a huge amount of complexity with passwords, tokens, multi factor checks, user directories, compliance rules and now, AI agents that also need to prove who they are.&lt;/p&gt;

&lt;p&gt;WSO2 has built three different products to solve this problem. WSO2 Identity Server, Identity Platform and WSO2 ThunderID. At first glance this can look confusing. Why does one company need three identity products? The short answer is that each one was built for a different situation, a different type of team and in ThunderID’s case, a different era of technology altogether.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwm4xtoq4hdy6kxxm253b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwm4xtoq4hdy6kxxm253b.png" alt=" " width="640" height="159"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article walks through what each product is, how it is built, what it is good at and why all three continue to exist side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Products at a Glance
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgimndh0mustq9zrhovpe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgimndh0mustq9zrhovpe.png" alt=" " width="640" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. WSO2 Identity Server: The Original, Full Control Option
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;WSO2 Identity Server is the oldest and most complete of the three. It has been developed for over a decade and is used by large organizations such as banks, telecoms and government bodies. It is open source, which means the code is publicly available, but serious deployments use the commercial support version.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it is built
&lt;/h2&gt;

&lt;p&gt;WSO2 IS is written in Java and runs on something called the “Carbon” platform, which is WSO2’s own internal framework used across many of their products. Think of it as one large application that you install on your own servers (or your own cloud account) and it stays running there permanently, like a piece of infrastructure you own.&lt;/p&gt;

&lt;p&gt;A simple picture of how it fits into a company’s systems is as below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqchul2csp2mo4vgov0jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqchul2csp2mo4vgov0jq.png" alt=" " width="640" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Strengths
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Full control. Nothing leaves your own infrastructure, which matters a lot for banks, hospitals and governments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Very deep customization. You can change almost anything about how it behaves.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Strong support for federation, meaning it can connect to many other identity systems (social logins, corporate directories, government ID systems and so on).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Built in tools for governance and consent, which are important for privacy laws.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trade offs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Someone has to install it, patch it and keep it running. This takes real engineering time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Because it does so much, it can feel heavy and slower to set up for smaller projects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The interface, while improved recently, still feels built for enterprise IT teams rather than individual developers moving fast.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who it fits
&lt;/h2&gt;

&lt;p&gt;Large organizations with strict compliance needs, dedicated infrastructure teams and a requirement that user data never leaves their own systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. WSO2 Identity Platform: The Same Power, Delivered as a Cloud Service
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;Formerly known as Asgardeo, WSO2 Identity Platform is built using the same core identity engine as WSO2 Identity Server. In other words, it is not a different product from scratch. It is the same proven technology, but instead of installing it yourself, WSO2 runs it for you in the cloud and you simply connect to it.&lt;/p&gt;

&lt;p&gt;This is the same idea as the difference between running your own email server versus using Gmail. The underlying concepts are similar, but one requires you to manage servers and the other does not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpm4fgixc3bjntgb6vltl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpm4fgixc3bjntgb6vltl.png" alt=" " width="640" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Identity Platform can also be deployed as a “single tenant private cloud” for companies that want cloud convenience but with their own isolated environment. That is a middle ground between full self-hosting and shared cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strengths
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;No servers to install or patch. You sign up and start building right away.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Newer AI powered features, such as describing a login flow in plain English and having the system build it automatically and automatic branding of login screens.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Predictable, cloud style pricing compared to some competitors.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trade offs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your identity data lives on WSO2’s cloud infrastructure rather than your own, which may not suit organizations with strict data residency rules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Less low level control than the self hosted Identity Server since you are working within a managed service.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who it fits
&lt;/h2&gt;

&lt;p&gt;Startups, product teams and companies that want enterprise grade identity features without hiring a team to maintain servers. It is WSO2’s direct answer to services like Auth0.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. WSO2 ThunderID: Built for a New Kind of User, the AI Agent
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;ThunderID is the newest of the three and it is built completely differently under the hood. Where Identity Server and Identity Platform share a large Java based core, ThunderID is written in Go, a programming language known for being fast, lightweight and easy to package into small containers.&lt;/p&gt;

&lt;p&gt;ThunderID was created because the identity world is changing. It is no longer just humans logging in. Now, software “agents” (AI systems that act on behalf of a person or a company) also need an identity, need permissions, and need to be tracked and trusted. Traditional identity systems were never designed with that in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it is built
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhhctoyeqiog58kvehhxc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhhctoyeqiog58kvehhxc.png" alt=" " width="640" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Strengths
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Very lightweight and fast to start, which fits naturally into modern container-based systems (the kind of setup used by cloud native teams today).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Treats AI agents as “first class citizens,” meaning an agent can have its own identity, its own limited permissions and a clear record of what it did and on whose behalf.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Supports newer identity ideas like digital wallets and verifiable credentials (a modern, more private way of proving facts about yourself, similar to a digital ID card).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Built with future proof encryption in mind, so it stays secure even as computing power increases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Designed to be simple to run anywhere, whether on a laptop for testing or across a large cloud deployment.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trade offs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It is still new and under active development, so it does not yet have the years of enterprise track record that Identity Server has.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some advanced enterprise features found in Identity Server may take time to appear in ThunderID.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who it fits
&lt;/h2&gt;

&lt;p&gt;Teams building modern, cloud native applications and especially teams working with AI agents that need to securely access data, tools or other systems on behalf of a user or company.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the OAuth2 / OIDC Login Flow Actually Works
&lt;/h2&gt;

&lt;p&gt;All three products (Identity Server, Identity Platform and ThunderID) are built on the same two industry standards, OAuth2 and OIDC. These are not WSO2 inventions. They are open standards used by almost every login system in the world, including Google, Microsoft and Facebook logins. Understanding this flow helps explain what all three products are actually doing underneath.&lt;/p&gt;

&lt;p&gt;Here is the simple version of what these two words mean.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;OAuth2 answers the question “what is this app allowed to do.” It is about permission.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;OIDC (OpenID Connect) is built on top of OAuth2 and answers the question “who is this person.” It is about identity.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of OAuth2 as a hotel key card system and OIDC as the ID check at the front desk. The front desk checks who you are (OIDC), then hands you a key card that only opens certain doors (OAuth2).&lt;/p&gt;

&lt;p&gt;Think of OAuth2 as a hotel key card system, and OIDC as the ID check at the front desk. The front desk checks who you are (OIDC), then hands you a key card that only opens certain doors (OAuth2).&lt;/p&gt;

&lt;h2&gt;
  
  
  The flow, step by step
&lt;/h2&gt;

&lt;p&gt;Imagine you are logging into a shopping app using your WSO2 Identity Platform account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnmx3hcie43els4fcvtbp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnmx3hcie43els4fcvtbp.png" alt=" " width="640" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the code and not just a direct password check
&lt;/h2&gt;

&lt;p&gt;You may notice the shopping app never actually sees your password. It only receives a short lived code, then trades that code for tokens. This is one of the most important ideas in modern identity systems. The app you are using should never touch your raw password. Only the identity system (WSO2 IS, WSO2 Identity Platform or ThunderID) ever sees it. This is exactly what all three products are built to manage safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the two tokens actually mean
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;ID Token: A small, signed piece of information that proves who you are. It usually contains your name, email, and a unique ID.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access Token: A separate piece of information that the shopping app can use to make requests on your behalf, such as “get this user’s order history.” It does not prove identity by itself. It proves permission.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation is the same across WSO2 Identity Server, Identity Platform, and ThunderID, because all three follow the same open standard. The real difference between them is not the flow itself, but where it runs (your own servers, WSO2’s cloud, or a lightweight container) and who or what is allowed to go through that flow (a human only, or also an AI agent, a service, or a decentralized digital wallet).&lt;/p&gt;

&lt;h2&gt;
  
  
  How WSO2 ThunderID Handles AI Agent Permissions
&lt;/h2&gt;

&lt;p&gt;This is the part of ThunderID that is genuinely new compared to older identity systems. Traditional identity systems like WSO2 IS were designed with one assumption: the thing logging in is a human being sitting at a keyboard. ThunderID was designed knowing that is no longer always true. Sometimes the thing logging in is an AI agent acting on a person’s behalf, with no human directly present at that moment.&lt;/p&gt;

&lt;p&gt;ThunderID handles this through a few connected ideas.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Agents get their own identity, not a borrowed one
&lt;/h2&gt;

&lt;p&gt;Instead of an AI agent secretly using a human’s saved password or personal login token, ThunderID gives the agent its own separate identity record. This means the system always knows, clearly, whether an action was taken by a human or by an agent acting for that human.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdrbxf5ricy0jc355a54m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdrbxf5ricy0jc355a54m.png" alt=" " width="620" height="868"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Delegated authority, not full impersonation
&lt;/h2&gt;

&lt;p&gt;This is one of the most important ideas in the whole system. There are two very different ways to let an agent act for you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Impersonation: the agent completely becomes you. The system can no longer tell the difference between you and the agent. This is risky, because if something goes wrong, it is hard to know who actually did it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Delegation: the agent keeps its own separate identity, but is granted specific, limited permission to act on your behalf. The system always keeps a clear record of “Agent X did this action, on behalf of User Y.”&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ThunderID is built around delegation rather than impersonation. This matters a lot for safety and trust, because it means an AI agent can be given exactly the permissions it needs and nothing more.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Consent aware access
&lt;/h2&gt;

&lt;p&gt;Before an agent is allowed to act, the system is designed to check that the human has actually agreed to it. This is similar to how a mobile app asks “allow this app to access your camera, yes or no.” ThunderID applies that same idea to AI agents. A human can grant an agent permission for a specific task, such as “you may check my calendar,” without giving it permission for everything else, such as “you may also read my private messages.”&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Traceability, meaning every action leaves a trail
&lt;/h2&gt;

&lt;p&gt;Every time an agent does something, ThunderID is designed to record that action, including which agent did it, under which permission and on whose behalf. This creates an audit trail. If something unexpected happens, it is possible to look back and see exactly what the agent was allowed to do and what it actually did.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Verifiable credentials for agents
&lt;/h2&gt;

&lt;p&gt;ThunderID can also issue what are called verifiable credentials to agents. A simple way to think about a verifiable credential is a digital, tamper proof certificate. Instead of just trusting an agent because it says who it is, the agent can present a signed credential that proves a fact about itself, for example, “this agent was created by Company X and is approved to access Service Y.” Other systems can check that this credential is real without having to call back and ask the original issuer every time.&lt;/p&gt;

&lt;p&gt;ThunderID supports the emerging standards for this, often referred to as OpenID4VCI (issuing credentials to a digital wallet) and OpenID4VP (presenting and verifying those credentials later). In plain terms:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmt68x6dautaz8a1tu7tj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmt68x6dautaz8a1tu7tj.png" alt=" " width="640" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;The overall idea behind ThunderID’s agent permission model is simple, even though the technology behind it is new. Give every agent its own identity, give it only the permissions it actually needs, keep a clear record of what it does and make sure it can prove who it is without needing a human to vouch for it every single time. This is very different from older systems, where the safest option was often just to give an application full access using someone’s personal login, which is exactly the kind of risk that becomes dangerous once agents start acting on their own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why All Three Exist Together
&lt;/h2&gt;

&lt;p&gt;It can help to think of these three products less as competitors and more as three answers to three different questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;“I need full control and my data can never leave my own servers.” → WSO2 Identity Server&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“I want enterprise grade identity but I do not want to manage servers.” → WSO2 Identity Platform&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“I am building modern, container-based software and some of my users are actually AI agents.” → WSO2 ThunderID&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are not fighting for the same job. They are built for different environments and different moments in a company’s growth. In fact, they often work together. For example, a company might use Identity Platform for its customer login, while using ThunderID as the identity layer inside an internal platform that manages AI agents and still keep Identity Server running for a legacy system that cannot be moved to the cloud.&lt;/p&gt;

&lt;p&gt;Side by Side Comparison&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdf6wkmo9qjhozjaw99tq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdf6wkmo9qjhozjaw99tq.png" alt=" " width="640" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;WSO2 Identity Server, Identity Platform and ThunderID are not three versions of the same idea. They represent three different philosophies about how identity should be delivered. Fully owned and controlled, conveniently delivered as a cloud service, or lightweight and built for the new world of AI agents and cloud native software.&lt;/p&gt;

&lt;p&gt;Identity Server exists because some organizations must keep everything in house. Identity Platform exists because most teams would rather focus on their product than manage servers. ThunderID exists because the definition of a “user” is expanding beyond just people and older systems were not designed for that shift.&lt;/p&gt;

&lt;p&gt;Understanding why all three exist is really about understanding how much identity management itself has changed, from a login box on a website, to a full system that now has to think about humans, machines and AI agents all at once.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>authentication</category>
      <category>security</category>
      <category>software</category>
    </item>
    <item>
      <title>The Cross-Lingual Coder: Python Practices That Translate Everywhere</title>
      <dc:creator>Pasindu Balasooriya</dc:creator>
      <pubDate>Tue, 08 Sep 2026 18:21:26 +0000</pubDate>
      <link>https://dev.to/pasindu_balasooriya/the-cross-lingual-coder-python-practices-that-translate-everywhere-4g19</link>
      <guid>https://dev.to/pasindu_balasooriya/the-cross-lingual-coder-python-practices-that-translate-everywhere-4g19</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmo6p0hei58d1vkmhmden.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmo6p0hei58d1vkmhmden.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a first-year Software Engineering undergraduate, I recently started learning Python, my first programming language that I learned methodically and in depth (disregarding some brief exposure to Java in the distant past). As I was working through my Python tutorials and cross-referencing Java resources ad hoc, it dawned on me that as prospective software engineers, it is important to keep your programming practices language-agnostic to the best extent possible.&lt;/p&gt;

&lt;p&gt;In this article, I’ll explore what language-agnostic practices mean, why it is important (believe me it is) and how you can approach your code with this universal mindset. We’ll also look at some code snippets to help you compare and contrast language-agnostic code with python-specific code.&lt;/p&gt;

&lt;p&gt;Language agnostic code refers to programming concepts, logic or algorithms that are independent of the programming language in which they are written. These practices are not tied to any specific programming language, meaning that the code or the idea can be applied across various programming languages with little to no adjustments. This focuses on logic and structure rather than syntax, thus keeping your code universal and/or easily translatable. Simply put, in this context it is about avoiding python-only shortcuts and instead using patterns that apply across multiple programming languages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Improves Flexibility and Portability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enables implementation of solutions in any language as needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simplifies transition of technologies (and on a more personal note, your job role, should you switch to a different primary programming language) without rewriting the entire algorithm.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Enhances collaboration and interoperability&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Facilitates understanding of developers from different backgrounds, the nuclear logic behind the implementations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ideal for teams using multiple languages (e.g., frontend vs backend), to streamline seamless communication.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Better learning and Guidance&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Encourages abstract thinking and analytical cognitive skills.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Focuses on understanding how the logic works instead of how to phrase the logic in a specific language.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Code reusability&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Accommodates reuse of code, written in a language independent way, in other projects with minimal adjustments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's look at some examples to cement the above concepts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let's consider a simple programme that prints even numbers less than 10.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In python the “in” operator can be used in a for loop with the range function to iterate over a scope of values, which is a very python-specific approach to the code.&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same solution to the given scenario can be implemented using a while loop with portable logic.&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;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Now let’s consider an instance where a programme checks whether a user given number is positive, negative, or neutral.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;“elif” is a keyword used specifically in python in the context of conditional statements to check for multiple sequential conditions.&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;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter a number: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Positive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Negative&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Zero&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same logic can be modeled in Python with language independence as follows using if and else statements.&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;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter a number: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Positive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Negative&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Zero&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Let’s dive into another example where you are asked to find the maximum of three user-given numbers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In Python, the built-in “max ()” function can be used to return the largest value among the provided arguments or within an iterable.&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter a: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter b: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter c: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Maximum is:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To keep the above solution more language-agnostic, we can check for the maximum value with an if condition and simultaneously store the highest value in a variable such that the maximum value is assigned to the variable eventually.&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter a: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter b: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter c: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;max_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;max_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;max_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;max_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Maximum is:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;String formatting is an indispensable part of your programme.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In Python, f-strings can be used to format strings as follows.&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same output can be reproduced using the method of concatenating strings with +, which is supported in many other programming languages.&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Another example can be drawn from file handling.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The “with open ()” statement in python ensures that files are properly closed irrespective of whether errors take place during file processing, a practice that is uniquely Python-oriented.&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="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a more language-neutral note, the same process can be implemented with distinct open, read and close operations as shown below.&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="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Now let’s check out an instance of Exception Handling.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In Python, the “except Exception as e” construct is used within the try-except block which captures the exception to the variable “e”.&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="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter a number: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;
&lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ZeroDivisionError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Result is:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same code snippet could be rewritten with a language-agnostic approach as follows.&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="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter a number: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Result is:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid input! Please enter a valid number.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ZeroDivisionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cannot divide by zero!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, on a more conclusive note, let me reiterate the fact that keeping your code language-agnostic is a vital practice whether you are a beginner or a seasoned coder. It ensures that your programme is portable, reusable and hassle-free to collaborate on with developers who might be using different languages. Focusing your approach on logic and structure as opposed to language-specific syntax makes you more adaptable and future-ready for any language switch or technology transition.&lt;/p&gt;

&lt;p&gt;At the end of the day, writing language-agnostic code isn’t just about excelling at syntax, it’s about mastering the art of thinking beyond borders and building solutions that transcend the practical limitation of a programming language in its singularity.&lt;/p&gt;

</description>
      <category>python</category>
      <category>bestpractices</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Heartbeats, Not Clicks: UX Under Extreme Pressure</title>
      <dc:creator>Pasindu Balasooriya</dc:creator>
      <pubDate>Tue, 08 Sep 2026 12:15:27 +0000</pubDate>
      <link>https://dev.to/pasindu_balasooriya/heartbeats-not-clicks-ux-under-extreme-pressure-4bmi</link>
      <guid>https://dev.to/pasindu_balasooriya/heartbeats-not-clicks-ux-under-extreme-pressure-4bmi</guid>
      <description>&lt;p&gt;When UX Is a Matter of Seconds: Designing for High-Stakes Interfaces&lt;/p&gt;

&lt;p&gt;In high stakes interface design, the difference between a good user experience and a failed one is not measured in clicks or conversions. It is measured in heartbeats. Whether you are designing for a cockpit, a surgical suite, or an emergency command center, your users are operating at the edge of human cognitive capacity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdqkdkh7g95mkxmv5q2mr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdqkdkh7g95mkxmv5q2mr.png" alt=" " width="720" height="731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why standard UX breaks under pressure
&lt;/h2&gt;

&lt;p&gt;Most modern UX is built for leisurely immersion. We want users to explore, compare, and discover. But in high-stress environments, the user’s mental model shifts to interruption-based action. The interface becomes a cognitive prosthetic.&lt;/p&gt;

&lt;p&gt;The question is not “How delightful is this?” The question is “How quickly can a human do the right thing under stress?”&lt;/p&gt;

&lt;p&gt;A quick example: emergency dispatch&lt;/p&gt;

&lt;p&gt;Imagine a dispatcher who has 30 seconds to get an ambulance moving. A traditional layout shows a rich record view, filters, and status tabs. In a high-stakes layout, the screen leads with the address, incident type, and a single “dispatch” action. Details sit one tap away, so the critical path stays clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  1) The logic of chaos: Hierarchical Task Analysis
&lt;/h2&gt;

&lt;p&gt;Before the first wireframe, perform a digital autopsy of the task using Hierarchical Task Analysis (HTA). Standard flows show the path, but HTA reveals the why and breaks the objective into sub-goals.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Identify cognitive bottlenecks where complexity overwhelms working memory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Map sequence pressure so deep-work screens prioritize findability and wearables prioritize glanceability.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2) The math of survival: Fitts’s and Hick’s laws
&lt;/h2&gt;

&lt;p&gt;In high-pressure design, mathematical models are predictive tools for human performance.&lt;/p&gt;

&lt;p&gt;Fitts’s Law states that the time to acquire a target is a function of distance and size:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Place primary actions where the cursor naturally lands after a scan (often the bottom-right on desktop).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On mobile or wearable devices used in motion, expand targets to the edges to reduce miss-taps.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hick’s Law predicts that decision time increases with the number of choices:&lt;/p&gt;

&lt;p&gt;In a crisis, reduce $n$ to the minimum that still keeps people safe. Fewer options mean less time spent thinking and more time acting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg5531ydnyc88ldwzz0zh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg5531ydnyc88ldwzz0zh.png" alt=" " width="720" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3) Protecting working memory
&lt;/h2&gt;

&lt;p&gt;Cognitive Load Theory reminds us that the brain can only process a limited amount of information at once. When that limit is reached, you get a bottleneck.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Progressive disclosure: hide secondary details behind explicit triggers so the primary task stays clean.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The 8pt grid: a strict grid creates predictable rhythm. In a crisis, predictability is the antidote to panic.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4) The “shrunken website” fallacy
&lt;/h2&gt;

&lt;p&gt;The biggest mistake in high-stakes design is shrinking a desktop dashboard to fit a watch face.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Desktop users are immersed and can handle density.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wearable users are interrupted and need a flattened hierarchy and linear controls.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion: designing for survival
&lt;/h2&gt;

&lt;p&gt;High-stakes interface design is not about aesthetics. It is about the physics of human cognition. When we apply HTA, Fitts’s Law, and Hick’s Law, we do not just build better apps. We build systems that protect human performance when it matters most.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Design for interruption, not exploration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use HTA to expose cognitive bottlenecks early.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Minimize choices to cut decision time under stress.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Favor glanceability on wearables and findability on deep-work screens.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>design</category>
      <category>software</category>
      <category>ux</category>
    </item>
    <item>
      <title>ACID vs BASE is a false dichotomy: Why your system needs both</title>
      <dc:creator>Pasindu Balasooriya</dc:creator>
      <pubDate>Tue, 08 Sep 2026 12:09:12 +0000</pubDate>
      <link>https://dev.to/pasindu_balasooriya/acid-vs-base-is-a-false-dichotomy-why-your-system-needs-both-59h8</link>
      <guid>https://dev.to/pasindu_balasooriya/acid-vs-base-is-a-false-dichotomy-why-your-system-needs-both-59h8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5rc1tcqjszsd6a9fr9jk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5rc1tcqjszsd6a9fr9jk.png" alt=" " width="720" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The architect’s dilemma
&lt;/h2&gt;

&lt;p&gt;If you have ever designed a distributed system, you have faced the classic dilemma. On one side are the ACID principles with all that they bring, Atomicity, Consistency, Isolation, and Durability. This is the world of banks and monetary transactions, with tight security where data integrity is of paramount importance and every transaction guarantees meticulous precision. But this strong consistency comes at the cost of performance.&lt;/p&gt;

&lt;p&gt;On the other side you have BASE (Basically Available, Soft State, Eventually Consistent). This is the world of social media feeds and high traffic websites with high availability and scalability. Here, strong consistency is reduced to eventual consistency where data will be consistent with comparable latency.&lt;/p&gt;

&lt;p&gt;For years, architects have been told they must choose but one at the expense of the other. It is a compromise that feels like you are leaving value on the table either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hybrid solution
&lt;/h2&gt;

&lt;p&gt;A mature modern architecture should not force you to pick just one. Instead, it should recognize a platform with its entire complexity that an application is not a singular entity, rather, it’s a collection of different jobs. For instance, a payment service has different needs than a product catalog, as does an inventory service compared to a user profile service.&lt;/p&gt;

&lt;p&gt;The solution to this, ideally in a distributed system with a microservices architecture, is to adopt a hybrid data strategy. Instead of a “one size fits all approach”, we can apply different data models to different parts of the application based on their requirement and business logic. We can achieve both strong consistency and high availability where they are needed in the microservices. This allows us to achieve perfect data integrity for critical operations while simultaneously delivering the high availability and performance the users expect for less critical operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real-world blueprint on architecting for a global retailer
&lt;/h2&gt;

&lt;p&gt;Consider a mid-sized global apparel company with branches in Europe, Asia and America and E-commerce presence in those continents, whose rapid growth outpaced their IT infrastructure, leading to a classic conflict between operational needs and customer experience.&lt;/p&gt;

&lt;p&gt;They were facing two different demands, one no less important than the other.&lt;/p&gt;

&lt;p&gt;1.Inventory accuracy&lt;/p&gt;

&lt;p&gt;When a customer buys a pair of jeans online, the inventory system must be 100% accurate immediately across the globe. An error here could mean overselling stock, leading to order cancellations, with customer dissatisfaction and logistical catastrophes following soon after. This part of the business hence demands perfect precision.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fast and engaging storefront&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the same time, customers browsing the website expect a fast, seamless experience. They would want to see product catalogs load instantly and their user profiles must be responsive all the time. This part of the business demands high availability and top-tier performance.&lt;/p&gt;

&lt;p&gt;This is where a single, monolithic database begins to struggle. Forcing the entire system to operate with strict rules of the inventory makes the storefront less responsive just as well as letting the whole system abiding by the eventual consistency principles risks the integrity of financial and stock data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd4whoy8xed0czj9o4f8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd4whoy8xed0czj9o4f8.png" alt=" " width="720" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First let us talk about the parts of the business that necessitate zero margin for errors, like inventory, orders and payments. This is the financial and logistical backbone of the company where an error is not just a bug, it’s revenue loss and customer dissatisfaction. The primary goal of such services is strong consistency. To this end, we use a synchronous multi-master replication strategy, a diplomatic choice that introduces both safety and reliability.&lt;/p&gt;

&lt;p&gt;Now let’s break that down.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Synchronous&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a transaction is processed, the transaction is not confirmed until every single regional database/node has received the update and acknowledged it. This guarantees strong consistency and is ACID-compliant. For instance, the inventory count would always be accurate across the globe at any given moment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-master&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any regional node can accept writes (e.g., processing an order). This provides high availability and eliminates the single point of failure while ensuring that an outage in a regional node does not stop the entire company from functioning.&lt;/p&gt;

&lt;p&gt;This approach is the digital equivalent of a bank transaction, built on ACID principles. To implement this, we could use a battle-tested relational database like PostgreSQL or a managed cloud service designed for the exact same purpose as Amazon Aurora. The trade-off is slight increase in write time but it’s a price worth paying to ensure security and accuracy.&lt;/p&gt;

&lt;p&gt;Next, let’s talk about parts of the business that interact with customers directly like product catalogs, user profiles and recommendations. The primary goal of these services is to be fast, engaging and always online. To this end, we use an asynchronous, master-slave replication strategy.&lt;/p&gt;

&lt;p&gt;Here’s what that means.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asynchronous&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a user updates their profile, the change is written to a primary master node, and a success message is immediately returned to the application layer. The update is then propagated to the rest of the read replicas in the background, without making the user wait (reduced latency).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Master-slave&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single master database handles all writes, ensuring a simple, conflict-free process. Meanwhile, read-only slave replicas in other regions handle read requests that improves read speed and load balancing of read requests.&lt;/p&gt;

&lt;p&gt;This approach leads to eventual consistency and is BASE-compliant. There might be a delay of few seconds before a change is globally visible which is an acceptable trade-off for a highly responsive platform. To implement this, we could use a database like MongoDB or a highly scalable cloud-based NoSQL database like Amazon DynamoDB or Cassandra.&lt;/p&gt;

&lt;h2&gt;
  
  
  The key takeaway — design with intent
&lt;/h2&gt;

&lt;p&gt;For years, the debate between ACID and BASE has been viewed as a battle of principle. But as we have seen, the most effective solution is not about choosing a side, and it is not always about dichotomy but holism every now then. It involves recognizing that a complex system is not a monolith with a single set of needs but rather a diverse ecosystem with different business logic and functions with unique requirements.&lt;/p&gt;

&lt;p&gt;The inventory system’s job is to be an infallible ledger while the product catalog’s job is to be fast and engaging. By using a hybrid strategy, we allow each part of our system to be excellent at its specific task, rather than forcing the entire system to settle for a “one size fits all” compromise.&lt;/p&gt;

&lt;p&gt;Thus, the next time you are faced with architect’s dilemma, remember to design with intent. Analyze the business domain, understand the trade-offs and build a system that uses the right strategy for the right use case. The most resilient of systems do not choose a side. Instead, they build a bridge between them.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>scalability</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Dynamic Typing Is Fun, Until It Isn’t (Lessons from Python and C#)</title>
      <dc:creator>Pasindu Balasooriya</dc:creator>
      <pubDate>Tue, 08 Sep 2026 12:00:56 +0000</pubDate>
      <link>https://dev.to/pasindu_balasooriya/dynamic-typing-is-fun-until-it-isnt-lessons-from-python-and-c-176p</link>
      <guid>https://dev.to/pasindu_balasooriya/dynamic-typing-is-fun-until-it-isnt-lessons-from-python-and-c-176p</guid>
      <description>&lt;p&gt;As an undergraduate in Software Engineering, I began my programming journey with Python. Its basic simplicity, ease of use, flexibility and versatility made it easy for an amateur like me to understand the complex with less complexity. Python’s dynamic typing and concise syntax allowed me to write programmes quickly, experiment with different approaches and focus on solving problems. But when I started learning C#, I had to deal with a whole new level of static typing which completely changed my approach to coding. With that being said, having learnt python with a language-agnostic slant, the transition was not difficult; rather it was novel.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To learn more about my language-independent coding, read my article &lt;a href="https://medium.com/@pasindudilshanbalasooriya/the-cross-lingual-coder-python-practices-that-translate-everywhere-cfc3056db9dd" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To learn more about my language-independent coding, read my article here.&lt;/p&gt;

&lt;p&gt;In this article, I will explain how learning C# after Python made me appreciate the benefits of static typing, and how it improved my coding practices thus making my code reliable and easier to maintain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2legzsr1fewl9wlbi7up.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2legzsr1fewl9wlbi7up.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both static typing and dynamic typing refer to how programming languages handle variable types. But they differ in when type checking occurs.&lt;/p&gt;

&lt;p&gt;In static typing the type of any given variable is known at compile-time (this refers to the phase when the source code is translated into the bytecode). That means you must define the type of each variable when you declare it. Should there be a type mismatch the compiler catches the error before the programme is executed and throws a compile-time error.&lt;/p&gt;

&lt;p&gt;In contrast, Python is dynamically typed, meaning that the type of variable is determined at runtime (refers to when the bytecode is being executed). Definition of variable type is not needed upon declaration as the interpreter checks the types as the code is executed.&lt;/p&gt;

&lt;p&gt;Let’s explore the differences using a few code examples.&lt;/p&gt;

&lt;p&gt;Python — Dynamic typing&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;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello world!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; 
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python, being very flexible, has no constraints on variable types. As in the above example, you can assign an integer value to a variable and later change it to a string without any sort of error being returned. While this is great for quality prototyping, this can also lead to unexpected bugs during runtime.&lt;/p&gt;

&lt;p&gt;C# — Static typing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello, world!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Error&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In C#, type mismatch is immediately identified by the compiler and an error is thrown, should you attempt to assign an integer to a string variable. This prevents runtime type errors making your code more predictable and cleaner.&lt;/p&gt;

&lt;p&gt;Both Python and C# provide structures to capture multiple values with varying implementation strategies and type checking.&lt;/p&gt;

&lt;p&gt;Python Lists (dynamic)&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;my_list&lt;/span&gt; &lt;span class="o"&gt;=&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.14&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Python, you can store different types of data in the same list without issues. While this increases flexibility, you are at a disadvantage because you do not know the types of elements ahead of time.&lt;/p&gt;

&lt;p&gt;C# Arrays (static)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;myArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, myArray is an integer array and you cannot assign a string to one of its elements. This guarantees that the array will only contain integers, and any operations done on it will be type-safe (meaning that the compiler ensures operations are performed only on compatible types).&lt;/p&gt;

&lt;p&gt;Dynamic typing in classes using Python&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; makes a sound.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;dog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dog&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python allows you to create classes without explicitly defining types for properties, making it easier to work with but harder to detect errors.&lt;/p&gt;

&lt;p&gt;Static typing in classes using C#&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; makes a sound."&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;Animal&lt;/span&gt; &lt;span class="n"&gt;dog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Dog"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Speak&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In C#, properties like Name have clearly defined types (string), and the compiler will enforce these types throughout the code. This makes the code more robust (meaning that the code is strong, resilient and able to handle unexpected inputs without crashing) and less prone to runtime errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1) Early error detection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Static typing helps you catch errors early in the development process and with languages like C#, the compiler ensures that you are working with correct types, preventing many common mistakes that might only surface at runtime in dynamically typed languages like Python.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PrintSum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;PrintSum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"20"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As shown above, the C# compiler will immediately flag the error because you are trying to pass a string to a method that expects an integer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2) Code clarity and readability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With static typing, the data types are explicit, which makes the code more predictable and easier to understand. When you can see the clearly defined variable of type int or string, you know exactly what kind of data you are working with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;greeting&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello, world!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In contrast, Python does not provide this calibre of clarity, where data types are implicitly handled.&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;greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, world!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# No explicit type declaration
&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3) Improved refactoring and IDE support&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Static typing allows your Intergrated Development Environment (IDE) to provide better support for refactoring (the process of restructuring code without changing the functionality for better readability), and auto-completion tools. Because these types are defined at compile time, IDEs can predict the methods and properties you will need, leading to accurate and accelerated code changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;why static typing makes refactoring easier&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the main advantages of static typing is how it simplifies refactoring. In Python, you can easily change variable names, data types, and function signatures. But the risk is that you might break something unknowingly as there is no compile-time checking.&lt;/p&gt;

&lt;p&gt;In C#, the static typing system ensures that the compiler will let you know if you change a function’s signature and a consequent type mismatch occurs. This comes in handy when large codebases are concerned where making changes could affect multiple parts of the project.&lt;/p&gt;

&lt;p&gt;In the end, learning C# after Python helped me understand how valuable static typing can be. It made my code more predictable, easier to understand, reduced silly mistakes, and taught me to think more carefully about how to implement a solution with robust code. While Python is great for getting started quickly and obviously many other things, learning a statically typed language taught me structure and precision. With that being said, knowing both inarguably made me a better, well-rounded programmer.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Operator’s Identity Crisis: Polymorphism of the plus (+) Operator</title>
      <dc:creator>Pasindu Balasooriya</dc:creator>
      <pubDate>Tue, 08 Sep 2026 11:54:17 +0000</pubDate>
      <link>https://dev.to/pasindu_balasooriya/the-operators-identity-crisis-polymorphism-of-the-plus-operator-51l8</link>
      <guid>https://dev.to/pasindu_balasooriya/the-operators-identity-crisis-polymorphism-of-the-plus-operator-51l8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fea4eogcc3uc1ss8ivadi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fea4eogcc3uc1ss8ivadi.png" alt=" " width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few months ago, as a beginner in programming, it always made me wonder how the + operator behaves with such functional versatility across various use cases in Python, which is my first programming language. Having deliberated this dynamic, a few clicks on google made me realise that this concept exists and that it’s called a polymorphic behaviour. In this article, I will walk you through a library of codes that I came across later when I started working with different programming languages which solidified my understanding of this theory.&lt;/p&gt;

&lt;p&gt;Polymorphism is a significant concept in object-oriented programming which enables a single and otherwise exclusive function to behave differently based on context. While polymorphism is often coupled with functions, operators such as the plus (+) operator exhibit this behavior just as well. This article explores how the plus operator demonstrates polymorphism across various programming languages. From numeric addition of the unary + to string concatenation, the + operator shows different masks depending on the data type being interacted with.&lt;/p&gt;

&lt;p&gt;Simply put, polymorphism accommodates a single interface to represent different underlying formats or forms. In programming, polymorphism is generally associated with functions or operators when and where their performance and by extension, behavior changes according to the data type. The most typical and commonplace example is the instance of how a method behaves differently when it is called depending on the class of the object on which it is called. Beyond this, polymorphism also applies to operators.&lt;/p&gt;

&lt;p&gt;In languages that support operator overloading(This refers to the ability to redefine or customise the behaviour of operators like +,-,*,/, with user defined data types like classes. Programming languages like C++ allows operator overloading. While java does not natively support this, the sole exception is the + operator.) ,or implicit type coercion (the automatic conversion of a value from one data type to another, without explicit manipulation from the programmer), the + operator can perform a varied list of tasks. Understanding the polymorphism of the + operator is important to mastering the subtleties of different programming languages and concepts.&lt;/p&gt;

&lt;p&gt;The plus operator serves multiple roles depending on the context in which they are being used. Now let’s have a look at some code snippets that I happened across during studies, where you can compare and contrast to get a concrete understanding.&lt;/p&gt;

&lt;p&gt;The + operator is used most commonly to perform arithmetic addition which is the standard behaviour in all the programming languages I have worked with so far.&lt;/p&gt;

&lt;p&gt;Python:&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;  &lt;span class="c1"&gt;# Output: 8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Output: 8&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Output: 8&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript:&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;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// result is 8&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In many programming languages, the + plus operator also functions as the key binding element in string concatenation.&lt;/p&gt;

&lt;p&gt;JavaScript:&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;let&lt;/span&gt; &lt;span class="nx"&gt;greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Output: "Hello, World!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello, "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"World!"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: "Hello, World!"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;greeting&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello, "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"World!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: "Hello, World!"&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;Python:&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;greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;World!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: "Hello, World!"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In some languages like Python, the + operator can also be used to concatenate(join) lists or arrays instead of the extend() method.&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;list1&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;list2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;list1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;list2&lt;/span&gt;  &lt;span class="c1"&gt;# Output: [1, 2, 3, 4, 5, 6]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Java, the language not having accommodated operator overloading, attempting to do this would result in a compiler error. C# and JavaScript do not allow this action to be performed on their respective platforms as well.&lt;/p&gt;

&lt;p&gt;Type coercion or type casting refers to the process of converting a value from one data type to another.&lt;/p&gt;

&lt;p&gt;JavaScript is famous for its implicit type coercion. When the + operator is used with a number and a string, it converts the number to a string and concatenates them.&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;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Score: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: "Score: 10"&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;anotherResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;anotherResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: "51"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java also performs automatic type coercion when a String is one of the operands. The other operand (e.g., an int) is converted to its string representation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Agent "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: "Agent 7"&lt;/span&gt;

        &lt;span class="c1"&gt;// The integer 42 is coerced into the string "42"&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;anotherResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" is the answer."&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anotherResult&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: "42 is the answer."&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C# behaves very similarly to Java. The + operator triggers string concatenation if at least one operand is a string, converting the other types.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Level "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: "Level 99"&lt;/span&gt;

        &lt;span class="c1"&gt;// The integer 3 is coerced into the string "3"&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;anotherResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" lives remaining"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anotherResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: "3 lives remaining"&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;Python is more strict. It does not perform automatic type coercion between strings and numbers with the + operator. Trying to do so will raise a TypeError. You must explicitly convert the types when and where necessary.&lt;/p&gt;

&lt;p&gt;While polymorphism of the + operator can help simplify coding, it can also lead to confusion and errors if not properly understood in a platform dependent way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmasbtq2exz3d4xx1964h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmasbtq2exz3d4xx1964h.png" alt=" " width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the main challenges is implicit type casting in JavaScript which can lead to unexpected results when numbers and strings are combined, or when non-compatible data types are used.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In JavaScript, adding null or undefined values to a string can lead to unexpected behaviour.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Python will raise a TypeError should you attempt to concatenate incompatible types. (eg: A string and a number).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*Performance overhead (the additional resources such as time and memory that a process may consume beyond what is necessary to achieve a set goal) in JavaScript can arise due to type coercion ,especially when large datasets are considered.&lt;/p&gt;

&lt;p&gt;In order to avoid confusion and ensure smooth usage of the + operator, I recommend following these best practices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Be explicit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In dynamically typed languages like JavaScript, always make sure that your operands are of the same type before using + operator.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;2. Use type checking&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
In languages like Python, consider performing type checks using functions such as type() before using the + operator to avoid runtime errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Know your programming language&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Understand how + operator works in the language that you are working with, by referring to and reviewing documentation to see how it handles data types and coercion.&lt;/p&gt;

&lt;p&gt;The polymorphism of the + operator highlights the flexibility and the complexity inherent in many a programming language. Streching from numeric addition to string concatenation and more complex operations like list merging, the polymorphism of the + can be both helpful and challenging simultaneously. By understanding the behavior dynamics of the operator, we can write cleaner and more predictable code while avoiding pitfalls like type coercion. Irrespective of the programming language you are working with, understanding operator polymorphism is essential to master nuanced manipulation of data types.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>Containerization: A More Streamlined Approach to Deployment</title>
      <dc:creator>Pasindu Balasooriya</dc:creator>
      <pubDate>Tue, 08 Sep 2026 11:48:59 +0000</pubDate>
      <link>https://dev.to/pasindu_balasooriya/containerization-a-more-streamlined-approach-to-deployment-4f55</link>
      <guid>https://dev.to/pasindu_balasooriya/containerization-a-more-streamlined-approach-to-deployment-4f55</guid>
      <description>&lt;p&gt;As a beginner in Software Engineering, I understand quite well for my own good, how complex deploying an application can turn out to be, especially when resource management and performance optimization is concerned. Before containers came into play (and no, BC does not mean ‘before containerization’) developers relied mainly on VMs which is short for Virtual Machines, to deploy applications. While VMs were a solid way to create isolated environments that accommodated applications to be run independently from the OS of the host machine, they also came with their own set of challenges, when it comes to effectiveness. Now let me elaborate on this last line a little bit more.&lt;/p&gt;

&lt;p&gt;Virtual machines, once being the go-to solution for application deployment in isolated environments, had their own set of drawbacks even though they provided an entire OS for each application to simulate a server environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.Heavy Resource Consumption&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each application deployment requires allocation of resources for an entire OS as each VM requires a full operating system to run and this results in excessive resource usage with the VM consuming memory and CPU power which is as resource-intensive and no less inefficient as building a new city every time you want to add a new house.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd15sdig3i3zv9cfcs8lf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd15sdig3i3zv9cfcs8lf.png" alt=" " width="720" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.Slow Deployment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since VMs entail booting up the entire OS, starting up an application is time-consuming and considerably slow meaning that VMs are not simply fast enough should you need to deploy and scale applications quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Environment Inconsistency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While VMs are isolated, they can still differ from one another in terms of configuration, giving rise to the famous or rather the infamous “It works on my machine” problem. For instance, an application may work in your local VM seamlessly but fail to do so when deployed in production due to subtle configuration differences.&lt;/p&gt;

&lt;p&gt;The above drawbacks paved the way for the emergence of containerization. Containers, dissimilar to VMs, do not require their own operating system. Instead, they package the application with everything it needs to run such as packages, binaries and configurations into a lightweight, isolated environment.&lt;/p&gt;

&lt;p&gt;Containerization is hot right now 💅 and here’s why&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Resource Efficiency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Containers eliminate the need for their own OS for each instance by sharing the kernel of the host OS making them much lighter and resource efficient. A practical analogy to this would be using a cost-effective bicycle for a short trip instead of a gas-guzzling car.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Faster Deployment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Starting up almost instantly, containers remove the need to boot the full&lt;/p&gt;

&lt;p&gt;OS giving them enhanced productivity, much similar to the quick and efficient act of flipping a light switch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Consistency Across Environments&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Containers ensure that your application will run the same across all development, testing, and production environments, eliminating the “It works on my machine” problem we talked about before. Rest assured, if it works in the container in your laptop, you won’t hear your colleague complain (about the container, that is, at any rate)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3s74g1hkamdd4p00t84t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3s74g1hkamdd4p00t84t.png" alt=" " width="720" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Real-life Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine you are managing an E-commerce platform deployed on a VM which is gaining popularity. During the holidays, traffic spikes significantly and the VM struggles as demand increases. As VMs consume a large number of resources, they cause slowdowns and resource allocation issues and decelerates scaling. On top of all that, the “It works on my machine” problem arises due to environment inconsistency.&lt;/p&gt;

&lt;p&gt;However, if your platform used Docker containers managed by Kubernetes, scaling would be much faster and more efficient. Containers being lightweight allows you to run more instances with lesser resource consumption. Kubernetes would automate scaling to optimize peak hour performance. Additionally, Docker harmonizes consistency across all environments removing the configuration dissimilarities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Docker is a platform that simplifies the process of creating, deploying, and running containers, allowing developers to package the application along with its dependencies, into a single container.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Docker Image- This is the blueprint for creating containers which constitutes all the necessary configurations and dependencies needed to run the application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker Container- This is the actual running instance of the application created from the Docker Image.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes is the tool that manages the containers, orchestrating everything from scaling up containers to recovery(self-healing). (If Docker is the builder, Kubernetes is the project manager). This automates the deployment, scaling and operation of containerized applications and ensures effective resource allocation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl07rnn6b3gzu7bwv0ge2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl07rnn6b3gzu7bwv0ge2.png" alt=" " width="720" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s why containerization became a game-changer for modern application development.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Portability&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Containers can run on any machine that supports Docker, be it a local machine, physical server, or in the cloud, and you can deploy it from anywhere, much like packing your things into a suitcase.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Efficiency&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;More resource-efficient (less time-consuming and more cost-effective) because containers share the kernel of the host OS.&lt;/p&gt;

&lt;p&gt;3.Scalability&lt;/p&gt;

&lt;p&gt;Containers are easy to scale up and down. Kubernetes can add more automation to handle peak performance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Faster Development Cycle&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Testing, deployment and iteration, made easy by containerization, makes it easy to collaborate in teams and work on parallelly, leading to faster releases and fewer bugs.&lt;/p&gt;

&lt;p&gt;Adopting containerization was a revolutionary shift that modernized how we manage and deploy applications. Along with Docker and Kubernetes, containerization offers a lighter, faster way to run applications across environments. Much like moving from a traditional office building to a modular co-working space, you can work more efficiently, save resources and work faster. Containerization, supported by Docker and Kubernetes, is a modern solution that makes the deployment process more scalable, reliable, and portable.&lt;/p&gt;

</description>
      <category>deployment</category>
      <category>devops</category>
      <category>docker</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Inside WSO2-ThunderID’s Post-Quantum JWKS</title>
      <dc:creator>Pasindu Balasooriya</dc:creator>
      <pubDate>Tue, 25 Aug 2026 18:13:10 +0000</pubDate>
      <link>https://dev.to/pasindu_balasooriya/inside-wso2-thunderids-post-quantum-jwks-3433</link>
      <guid>https://dev.to/pasindu_balasooriya/inside-wso2-thunderids-post-quantum-jwks-3433</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgkb29si28fam34c2oh7t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgkb29si28fam34c2oh7t.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everyone worried about the wrong thing.&lt;/p&gt;

&lt;p&gt;When people talk about migrating to post-quantum cryptography, the first question is almost always performance. Will it slow down my auth server? Will verification cost me latency on every request?&lt;/p&gt;

&lt;p&gt;I spent a while measuring this inside ThunderID, an open source IAM stack that ships ML-DSA signing today and the performance answer turns out to be boring. Signing a token with ML-DSA is faster than RSA-2048. Verification is in the same ballpark as ECDSA.&lt;/p&gt;

&lt;p&gt;The problem is size. And size breaks things that speed never would.&lt;/p&gt;

&lt;h3&gt;
  
  
  First, here is one
&lt;/h3&gt;

&lt;p&gt;This is a real JWKS document, produced by ThunderID’s JWKS service with an ML-DSA-87 signing key loaded. I have trimmed the public key in the middle because the full thing is 3,456 characters of base64 and would eat half this article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keys"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"q_Gzy1Asrf8PhpN7ZFg2pR8_HADURDcvfEZOwqDHYXI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"kty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AKP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"use"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ML-DSA-87"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"pub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cxfw7KPEFQQyjHaDETwOayeFNC09h1tYRLxwkyvt4q_xV0WoqsH3OK...
              ...5GfdFtksLtA49sTwfiDp0iAZCplRqumBlc72"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at kty. Not RSA, not EC, not OKP. &lt;strong&gt;AKP&lt;/strong&gt; , for Algorithm Key Pair is a JWK key type introduced for exactly this. If your client library has a switch statement over key types and it almost certainly does, that switch does not have a branch for this yet.&lt;/p&gt;

&lt;p&gt;And the token it signs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;header : {"alg":"ML-DSA-87","kid":"mldsa87","typ":"JWT"}
payload : {"aud":"my-app","exp":1790000000,"iss":"https://localhost:8090",
           "scope":"openid profile email","sub":"8f3a-user"}
length : 6386 bytes (signature alone: 4627 bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six and a half kilobytes for a token carrying five claims. The claims are 118 bytes of that. Everything else is signature.&lt;/p&gt;

&lt;h3&gt;
  
  
  The numbers
&lt;/h3&gt;

&lt;p&gt;Here is what each algorithm costs. I generated these through ThunderID’s own signing path rather than copying them from a spec, so they include the base64url encoding you actually pay for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frnf9dt6u323bwe88a1x2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frnf9dt6u323bwe88a1x2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Going from ES256 to ML-DSA-87 takes a 302 byte token to roughly 6.4KB. That is about 21 times bigger. Against RS256 it is still more than 11 times.&lt;/p&gt;

&lt;p&gt;Timing, for contrast, was a non-event. RS256 signing sat around 630µs across runs. ML-DSA signing ranged from about 95µs to 520µs. Verification for everything landed between single digit and 60µs, close enough to noise at this resolution that I would not read anything into the ordering.&lt;/p&gt;

&lt;p&gt;That ML-DSA signing range is wide on purpose. ML-DSA uses rejection sampling, so it loops until it gets a signature meeting its constraints. Sometimes that is one pass, sometimes several. Benchmark it once and write down the number and you are quoting a coin flip. The sizes, by contrast, are fixed by the parameter set and did not move at all between runs.&lt;/p&gt;

&lt;p&gt;The CPU cost of post-quantum signatures has quietly stopped being the interesting problem. The bytes have not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where 6KB tokens actually hurt
&lt;/h3&gt;

&lt;p&gt;A 6KB token is fine sitting in a database. It stops being fine the moment it has to travel somewhere with a limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cookies cap at 4KB.&lt;/strong&gt; This is the one that bites first. RFC 6265 only requires browsers to support 4096 bytes per cookie and that is what they do in practice. An ML-DSA-87 token does not fit in a cookie at all. Neither does ML-DSA-65 once you add a realistic claim set. If your session design puts a signed token in a cookie, that design does not survive the migration without splitting the token or moving to a session reference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proxies cap request headers.&lt;/strong&gt; nginx defaults large_client_header_buffers to 8KB and plenty of API gateways sit in the same range. One 6KB bearer token in an Authorization header usually squeaks through. That token plus normal headers or two tokens on one request will not. These failures are miserable to debug because they surface as a generic 400 from an intermediary that never tells you which header was too big.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URLs cap hardest.&lt;/strong&gt; Anything putting a token in a query string or fragment is finished. The old advice about keeping URLs under 2,000 characters was already tight. A single post-quantum signature blows past it on its own.&lt;/p&gt;

&lt;p&gt;None of this is an argument against post-quantum. It is an argument for finding out now which parts of your architecture quietly assume a token fits somewhere because those assumptions stay invisible until they break.&lt;/p&gt;

&lt;h3&gt;
  
  
  The part where the standard library lets you down
&lt;/h3&gt;

&lt;p&gt;Here is the detail I did not expect.&lt;/p&gt;

&lt;p&gt;Go’s standard library has no ML-DSA support. Not in crypto, not in crypto/x509. So if you want to load an ML-DSA private key from a PKCS#8 file, which is how anyone normally ships a signing key, you parse the ASN.1 yourself.&lt;/p&gt;

&lt;p&gt;ThunderID has a file doing exactly that, and it is refreshingly honest about it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ML-DSA PKCS#8/ASN.1 key encoding helpers (RFC 9881). These fill the gap left&lt;/span&gt;
&lt;span class="c"&gt;// by the Go standard library's lack of ML-DSA support. When crypto/x509 gains&lt;/span&gt;
&lt;span class="c"&gt;// ML-DSA support (Go 1.27), delete this file and replace callers with&lt;/span&gt;
&lt;span class="c"&gt;// x509.ParsePKCS8PrivateKey / x509.MarshalPKCS8PrivateKey.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A whole file of hand-written ASN.1 with a note saying delete me when the language catches up. That is what post-quantum ready looks like in practice right now, and it is a long way from flipping a config value.&lt;/p&gt;

&lt;p&gt;It goes further. RFC 9881 lets an ML-DSA private key be encoded three different ways: as a seed, as an expanded key or as a SEQUENCE holding both. All three are valid, so the parser accepts all three:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ParseMLDSAPKCS8 parses a DER-encoded RFC 9881 PKCS#8 ML-DSA private key. It&lt;/span&gt;
&lt;span class="c"&gt;// accepts all three private-key CHOICE encodings (seed [0], expandedKey, and&lt;/span&gt;
&lt;span class="c"&gt;// the "both" SEQUENCE), preferring the seed when present.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are planning your own migration, that is the interop landmine worth knowing about. Whichever tool generates your key picks an encoding and whatever consumes it has to handle the one you got. “It’s a standard PKCS#8 file” is doing a lot of work in that sentence.&lt;/p&gt;

&lt;p&gt;The certificate side is stranger still. Go cannot parse an ML-DSA public key out of an X.509 certificate either, so the loader keeps the certificate DER as an opaque blob and derives the public key from the private key instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ML-DSA: the standard library cannot parse the certificate's public&lt;/span&gt;
&lt;span class="c"&gt;// key, so derive it from the configured private key.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything works. It just works around the language rather than with it and it will keep doing so until Go 1.27.&lt;/p&gt;

&lt;h3&gt;
  
  
  Crypto-agility is not a config flag
&lt;/h3&gt;

&lt;p&gt;The other thing that becomes obvious in a real implementation is how many places the algorithm choice reaches.&lt;/p&gt;

&lt;p&gt;It is not just the signing call. It is the JWKS endpoint, which has to publish the right alg and kid so relying parties can verify. It is key management and storage. It is the discovery document advertising what the server supports. It is per-client negotiation, because in any realistic migration different clients are ready at different times and a client that cannot verify ML-DSA needs to keep getting ES256 without anyone running a flag day.&lt;/p&gt;

&lt;p&gt;ThunderID’s config models this as a list of keys with an explicit choice of which one signs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;crypto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default-key"&lt;/span&gt;
      &lt;span class="na"&gt;cert_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config/certs/signing.cert"&lt;/span&gt;
      &lt;span class="na"&gt;key_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config/certs/signing.key"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mldsa87"&lt;/span&gt;
      &lt;span class="na"&gt;cert_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config/certs/mldsa87.cert"&lt;/span&gt;
      &lt;span class="na"&gt;key_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config/certs/mldsa87.key"&lt;/span&gt;

&lt;span class="na"&gt;jwt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;preferred_key_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default-key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple keys registered at once, one selected as preferred. That shape matters more than it looks. A migration is not a switch from old to new, it is a stretch of time where both are live, both are published in the JWKS and traffic moves over gradually. A design holding only one signing key at a time cannot express that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trying it yourself
&lt;/h3&gt;

&lt;p&gt;You need OpenSSL 3.5 or later to generate an ML-DSA key, which is the main friction right now. Most distributions are still on 3.0 to 3.4 and ML-DSA landed in 3.5.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl genpkey &lt;span class="nt"&gt;-algorithm&lt;/span&gt; ML-DSA-87 &lt;span class="nt"&gt;-out&lt;/span&gt; mldsa87.key
openssl req &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-key&lt;/span&gt; mldsa87.key &lt;span class="nt"&gt;-out&lt;/span&gt; mldsa87.cert &lt;span class="nt"&gt;-days&lt;/span&gt; 365 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/CN=thunderid-signing"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point a key entry at those two files, restart and the JWKS above is what you get.&lt;/p&gt;

&lt;p&gt;If your OpenSSL is older, the ThunderID repo carries generated ML-DSA fixtures for all three parameter sets under backend/internal/system/kmprovider/defaultkm/pki/testdata/, which is what I used for the output in this article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should you actually do this
&lt;/h3&gt;

&lt;p&gt;Not yet, for most people. The reason to care now is that the threat model was never about today.&lt;/p&gt;

&lt;p&gt;Harvest-now-decrypt-later means someone records your encrypted traffic today and decrypts it when the hardware arrives. The signature equivalent is trust-now-forge-later, an assertion you sign today with a long validity becomes forgeable the moment the underlying assumption falls. If you issue credentials meant to be trusted for years the clock on those started when you issued them, not when quantum computers show up.&lt;/p&gt;

&lt;p&gt;So the useful question is not “should I switch today” but “how long are my signatures meant to be trusted and do I know what breaks when I switch”. The second half you can answer this afternoon. Take your longest realistic token, add 6KB and go look at every place it travels.&lt;/p&gt;

&lt;p&gt;The measuring is cheap. The architectural assumptions you find are the expensive part and they do not get cheaper by waiting.&lt;/p&gt;

</description>
      <category>ami</category>
      <category>thunderid</category>
      <category>postquantumcryptogra</category>
      <category>wso2</category>
    </item>
    <item>
      <title>Markdown is the quietly powerful language you already almost know</title>
      <dc:creator>Pasindu Balasooriya</dc:creator>
      <pubDate>Wed, 12 Aug 2026 15:01:54 +0000</pubDate>
      <link>https://dev.to/pasindu_balasooriya/markdown-is-the-quietly-powerful-language-you-already-almost-know-416c</link>
      <guid>https://dev.to/pasindu_balasooriya/markdown-is-the-quietly-powerful-language-you-already-almost-know-416c</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fds3d9whyvk7fnhk94692.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fds3d9whyvk7fnhk94692.jpg" width="799" height="480"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://serokell.io/blog/markdown-editor-tips" rel="noopener noreferrer"&gt;https://serokell.io/blog/markdown-editor-tips&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You have probably written Markdown without realizing it. That asterisk you typed around a word in a Slack message to make it bold? Markdown. The hash symbol you threw before a heading in a README? Also Markdown. It is one of those rare tools that feels natural before you even learn it properly.&lt;/p&gt;

&lt;p&gt;This article is a complete guide to Markdown with what it is, why it matters and how to use it with real examples you can see rendered live.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is Markdown?
&lt;/h3&gt;

&lt;p&gt;Markdown is a lightweight markup language created by &lt;strong&gt;John Gruber&lt;/strong&gt; in 2004. The idea was simple. Write plain text that reads naturally and let a program convert it to formatted HTML. Instead of wrestling with &lt;/p&gt;
&lt;h1&gt; tags and &lt;strong&gt; wrappers, you write human-readable symbols that carry their own meaning.&lt;br&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Hello, World

This is a **very** important message.
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq1p3pq6zvmyy9e6yl3bt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq1p3pq6zvmyy9e6yl3bt.png" width="670" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same output. Far less noise.&lt;/p&gt;
&lt;h3&gt;
  
  
  Where Markdown is used
&lt;/h3&gt;

&lt;p&gt;Markdown is everywhere once you start looking. GitHub uses it for README files, issues and pull requests. Medium supports Markdown imports and keyboard shortcuts that mirror it. Notion, Obsidian and Confluence all render Markdown natively. Reddit uses a Markdown dialect for post formatting. Static site generators like Jekyll and Hugo build entire websites from Markdown files.&lt;/p&gt;

&lt;p&gt;Learning Markdown is a one-time investment that pays dividends across almost every platform a developer or writer touches.&lt;/p&gt;
&lt;h3&gt;
  
  
  The core syntax
&lt;/h3&gt;
&lt;h3&gt;
  
  
  Headings
&lt;/h3&gt;

&lt;p&gt;Use the # symbol to create headings. The number of # symbols corresponds to the heading level, from H1 to H6.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Heading 1
## Heading 2
### Heading 3
#### Heading 4
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftwz5rcq84ptexxan9k5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftwz5rcq84ptexxan9k5t.png" width="668" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bold and italic
&lt;/h3&gt;

&lt;p&gt;Wrap text in **double asterisks** for bold and *single asterisks* for italic. You can combine them.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is **bold text**.
This is *italic text*.
This is ***bold and italic*** at the same time.
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F10wro6dcizrd04qgl2p1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F10wro6dcizrd04qgl2p1.png" width="668" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Lists
&lt;/h3&gt;

&lt;p&gt;Unordered lists use -, * or + as bullet points. Ordered lists use numbers. You can nest lists by indenting with two or four spaces.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Unordered
&lt;/li&gt;
&lt;/ol&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Milk
- Eggs
- Bread
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyzsbguha9jlsi23kg9r2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyzsbguha9jlsi23kg9r2.png" width="666" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ordered
&lt;/li&gt;
&lt;/ol&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Clone the repository
2. Install dependencies
3. Run the dev server
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fescxd9sqh40swgb1twzk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fescxd9sqh40swgb1twzk.png" width="666" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Nested
&lt;/li&gt;
&lt;/ol&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Frontend
  - React
  - Tailwind CSS
- Backend
  - Node.js
  - PostgreSQ
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbxal0t3u19e2mho3us11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbxal0t3u19e2mho3us11.png" width="669" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Links and images
&lt;/h3&gt;

&lt;p&gt;Links follow the pattern &lt;a href="https://dev.toURL"&gt;link text&lt;/a&gt;. Images work the same way with an exclamation mark prepended. The text in square brackets becomes the alt text, which matters for accessibility.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read the [Markdown Guide](https://www.markdownguide.org) for more.
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1h894t1qgsa6aw71hwxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1h894t1qgsa6aw71hwxt.png" width="663" height="107"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Code
&lt;/h3&gt;

&lt;p&gt;For inline code, wrap it in backticks. For code blocks, use triple backticks and optionally specify the language for syntax highlighting.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inline code
&lt;/li&gt;
&lt;/ol&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the `console.log()` function to debug.
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4dnyp78h11yo6t5d308.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4dnyp78h11yo6t5d308.png" width="665" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Code block
&lt;/li&gt;
&lt;/ol&gt;

&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;
javascript&lt;br&gt;
function greet(name) {&lt;br&gt;
  return &lt;code&gt;Hello, ${name}!&lt;/code&gt;;&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5p7x3nred8fzoqn1iani.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5p7x3nred8fzoqn1iani.png" width="662" height="138"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Tables
&lt;/h3&gt;

&lt;p&gt;Tables use pipes | and hyphens - to define structure. The second row separates the header from the body. You can align columns by adding colons to the separator row.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Basic table
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| Name | Role | Status |
|------------|--------------|----------|
| Alice | Developer | Active |
| Bob | Designer | Active |
| Carol | QA Engineer | On leave |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;
plaintext&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft18da02m66dvvq13k3vc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft18da02m66dvvq13k3vc.png" width="666" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;With alignment
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| Left | Center | Right |
|:-----------|:------------:|---------:|
| aligned | aligned | aligned |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;
plaintext&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft9ud8kg2hds6u68bsa9g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft9ud8kg2hds6u68bsa9g.png" width="665" height="127"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Task lists
&lt;/h3&gt;

&lt;p&gt;GitHub Flavored Markdown adds interactive checkboxes using - [x] for checked and - [] for unchecked items.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- [x] Set up the project
- [x] Write the README
- [] Add unit tests
- [] Deploy to production
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg3rxghsblo3bxsw0mnbn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg3rxghsblo3bxsw0mnbn.png" width="665" height="162"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  A common mistake: escaping special characters
&lt;/h3&gt;

&lt;p&gt;What if you actually want to display an asterisk without triggering bold formatting? Use a backslash to escape it. The same trick works for #, [,], (, ) and other Markdown-reserved characters.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is \*not bold\*.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq8k6hfatb44ypu2zp6vp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq8k6hfatb44ypu2zp6vp.png" width="656" height="82"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  A real-world example: a project README
&lt;/h3&gt;

&lt;p&gt;Here is what a solid Markdown README looks like in practice. This is entirely plain text. No Word document, no HTML, no design tool. Just a .md file that renders beautifully on GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# MyApp

A lightweight REST API for managing personal tasks.

## Features

- Create, update, and delete tasks
- Assign due dates and priority levels
- Filter tasks by status

## Getting Started

### Prerequisites

- Node.js 18+
- PostgreSQL 14+

### Installation

1. Clone the repository:
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;
bash&lt;br&gt;
   git clone &lt;a href="https://github.com/yourname/myapp.git" rel="noopener noreferrer"&gt;https://github.com/yourname/myapp.git&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2. Install dependencies: `npm install`
3. Create a `.env` file based on `.env.example`.
4. Start the server: `npm run dev`

## License

MIT
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7ys61aaea4hvjo334m4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7ys61aaea4hvjo334m4.png" width="562" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Markdown beats rich text editors
&lt;/h3&gt;

&lt;p&gt;Rich text editors hide their formatting inside binary or XML structures you cannot read or version-control easily. Markdown files are plain text, which means:&lt;/p&gt;

&lt;p&gt;Version control works perfectly.&lt;/p&gt;

&lt;p&gt;Git can diff a .md file line by line and show exactly what changed. Try doing that with a .docx.&lt;/p&gt;

&lt;p&gt;They are portable.&lt;/p&gt;

&lt;p&gt;A Markdown file opened in 2040 will look exactly the same as it does today. File formats rot. Plain text does not.&lt;/p&gt;

&lt;p&gt;They are distraction-free.&lt;/p&gt;

&lt;p&gt;You are not hunting through toolbar menus. The formatting lives in the text itself, which keeps you focused on writing.&lt;/p&gt;

&lt;p&gt;They convert to almost anything.&lt;/p&gt;

&lt;p&gt;With tools like Pandoc, a single Markdown file can become a PDF, an HTML page, a Word document or an ePub.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started today
&lt;/h3&gt;

&lt;p&gt;You do not need to install anything. Open &lt;a href="https://stackedit.io/" rel="noopener noreferrer"&gt;StackEdit&lt;/a&gt; or &lt;a href="https://dillinger.io/" rel="noopener noreferrer"&gt;Dillinger&lt;/a&gt; in your browser. Type some Markdown on the left and watch it render on the right in real time.&lt;/p&gt;

&lt;p&gt;If you use VS Code, the built-in Markdown preview (Ctrl+Shift+V) is excellent. If you want a dedicated writing environment, Obsidian and Typora are both popular choices.&lt;/p&gt;

&lt;p&gt;The learning curve is genuinely shallow. Most people feel comfortable with the core syntax within an afternoon. What you get in return is a formatting language that works everywhere, lasts forever and never gets in your way.&lt;/p&gt;

&lt;p&gt;Start with a README for your next project. That is all it takes.&lt;/p&gt;

&lt;/strong&gt;
&lt;/h1&gt;

</description>
      <category>markdown</category>
      <category>md</category>
    </item>
    <item>
      <title>Deflation as Devotion: Shakespeare’s Subversion of Poetic Convention in Sonnet 130</title>
      <dc:creator>Pasindu Balasooriya</dc:creator>
      <pubDate>Thu, 23 Jul 2026 06:24:20 +0000</pubDate>
      <link>https://dev.to/pasindu_balasooriya/deflation-as-devotion-shakespeares-subversion-of-poetic-convention-in-sonnet-130-147b</link>
      <guid>https://dev.to/pasindu_balasooriya/deflation-as-devotion-shakespeares-subversion-of-poetic-convention-in-sonnet-130-147b</guid>
      <description>&lt;p&gt;&lt;em&gt;Shakespeare didn’t compare his lover to the sun and that’s exactly the point&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqp3mru4v5qisw6iiljwc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqp3mru4v5qisw6iiljwc.jpg" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;My mistress’ eyes are nothing like the sun;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Coral is far more red than her lips’ red;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;If snow be white, why then her breasts are dun;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;If hairs be wires, black wires grow on her head.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;I have seen roses damasked, red and white,&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;But no such roses see I in her cheeks;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;And in some perfumes is there more delight&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Than in the breath that from my mistress reeks.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;I love to hear her speak, yet well I know&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;That music hath a far more pleasing sound;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;I grant I never saw a goddess go;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;My mistress, when she walks, treads on the ground.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;And yet, by heaven, I think my love as rare&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;As any she belied with false compare.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sonnet 130, written by Shakespeare, is an avant-garde piece of literature that brings to light a canon of unprecedented and rule-breaking comparisons.&lt;/p&gt;

&lt;p&gt;The inaugural line of the octave, &lt;strong&gt;&lt;em&gt;“My mistress’ eyes are nothing like the Sun,”&lt;/em&gt;&lt;/strong&gt; connotes that his supposed lover’s eyes have but a radiance easily eclipsed by that of the sun. The orthodox poet, as opposed to the aforementioned line, would deem it unworthy to belittle his lover’s attributes in any context, and would instead produce a much more traditional line, one whose meaning is inverted to that of Shakespeare’s.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F500%2F0%2Ad5zqRtKQC7wHK9Ez" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F500%2F0%2Ad5zqRtKQC7wHK9Ez" width="500" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;“Coral is redder than her lips’ red;”&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The line quoted continues to elevate the poet’s unorthodox approach to normal comparison, depicting that his lover’s lips were much less red than that of a coral, a material which by itself does not characterise a redness worthy of poetic comparison. Shakespeare’s choice of coral is deliberate, and it is an object commonplace enough to undercut any romantic idealism, producing in the reader a sense of deflation where elevation is expected. In other works of literature, a reader would come across numerous instances where poets have compared lips to much esteemed redness, let alone corals, and have elevated the comparison to an idealism of its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;“I grant I never saw a goddess go; My mistress when she walks treads on the ground,”&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The above quoted lines in their singularity bring about a myriad of deflections from the poetic conventions of the time, as far as comparisons are concerned, especially alluding to a woman whose humanity is laid bare rather than mythologised, stripping away the divine pedestal that conventional poetry so readily constructed.&lt;/p&gt;

&lt;p&gt;The poet had never been able to spot a deity walk, partly because he had never wanted to look for whether they exist, or partly because they are not existential in reality at all. Either way, the presumed walk of a goddess is ideally an idyllic or a magical one, probably worthy of a woman of divine beauty. Shakespeare, through his then bold and debatable eye, sees his mistress’ walk much closer to that of an unhurried, earthly tread, the fabled moonwalk of a woman now juxtaposed to that of a modest and unassuming mere thumping devoid of feline grace. The significance here lies not in cruelty but in honesty; Shakespeare cannot compare what he has never witnessed, and so instead grounds his mistress firmly in the real world. This is divergence from convention at its best, and it is almost paradoxical that his woman’s walk is compared to and contrasted by a walk of a goddess, one that no mortal had ever set eyes upon before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;“I love to hear her speak, yet well I know That music hath a far more pleasant sound”&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A woman’s voice is traditionally associated with melodious musicality and is almost always expected to be soothing and heavenly to a man’s ears. The poet asserts that he loves to hear her speak but introduces an epiphany: &lt;strong&gt;&lt;em&gt;“Yet well I know,”&lt;/em&gt;&lt;/strong&gt; denoting that his enlightenment would surely diminish his liking of her speech, and the next immediate line goes on to confirm this. Shakespeare boldly confesses that music is far more melodious, far more enticing and enrapturing.&lt;/p&gt;

&lt;p&gt;Finally, it can be attested that such idyllic and fantastical romance does not have a place in Shakespeare’s vision of love, for it is in honest, unadorned affection, free of hollow comparisons, that he locates something far more enduring than idealism ever could.&lt;/p&gt;

</description>
      <category>englishliterature</category>
      <category>shakespeare</category>
      <category>creativewriting</category>
      <category>sonnet</category>
    </item>
  </channel>
</rss>
