<?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>Forem: Daniel Rusnok</title>
    <description>The latest articles on Forem by Daniel Rusnok (@danielrusnok).</description>
    <link>https://forem.com/danielrusnok</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F277448%2F9b1ff2d7-8527-418e-b70c-0e5335724bbe.jpg</url>
      <title>Forem: Daniel Rusnok</title>
      <link>https://forem.com/danielrusnok</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/danielrusnok"/>
    <language>en</language>
    <item>
      <title>I Thought I Understood Git. Then I Actually Learned the Difference Between Rebase and Merge.</title>
      <dc:creator>Daniel Rusnok</dc:creator>
      <pubDate>Wed, 25 Mar 2026 13:27:36 +0000</pubDate>
      <link>https://forem.com/danielrusnok/i-thought-i-understood-git-then-i-actually-learned-the-difference-between-rebase-and-merge-88g</link>
      <guid>https://forem.com/danielrusnok/i-thought-i-understood-git-then-i-actually-learned-the-difference-between-rebase-and-merge-88g</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.amazonaws.com%2Fuploads%2Farticles%2F5qlxow6dz17r0pfh1vaw.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.amazonaws.com%2Fuploads%2Farticles%2F5qlxow6dz17r0pfh1vaw.png" alt="Merge | Rebase graph" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used &lt;code&gt;git rebase&lt;/code&gt; long before I actually understood what it does.&lt;/p&gt;

&lt;p&gt;I picked it up from a senior developer on my first team. The rule was simple: &lt;em&gt;always rebase before you merge, keep the history clean.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I followed it. I typed the commands. I never questioned it.&lt;/p&gt;

&lt;p&gt;Then one day I rebased the wrong branch and rewrote a week of shared history. That was the day I finally sat down and learned what I had been doing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What merge actually does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;git merge&lt;/code&gt; takes two branches and joins them. It creates a new commit — a &lt;strong&gt;merge commit&lt;/strong&gt; — that has two parents: the tip of your branch and the tip of the branch you're merging into.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# You're on feature/my-feature&lt;/span&gt;
git merge main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a non-destructive operation. Nothing is rewritten. Your history grows a new node with two parents, and both lines of development are preserved exactly as they happened.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      A---B---C  feature
     /         \
D---E---F---G---H  main (merge commit)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The upside&lt;/strong&gt; — it's safe.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;The downside&lt;/strong&gt; — if you merge large feature branches frequently, the commit history can become harder to read.&lt;/p&gt;


&lt;h2&gt;
  
  
  What rebase actually does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;git rebase&lt;/code&gt; moves your commits. It takes the commits on your branch and replays them on top of another branch, one by one, as if you had started your work from there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# You're on feature/my-feature&lt;/span&gt;
git rebase main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Before
      A---B---C  feature
     /
D---E---F---G  main

# After
              A'--B'--C'  feature
             /
D---E---F---G  main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the prime marks — &lt;code&gt;A'&lt;/code&gt;, &lt;code&gt;B'&lt;/code&gt;, &lt;code&gt;C'&lt;/code&gt; are &lt;strong&gt;new commits&lt;/strong&gt;. Same changes, new hashes. Rebase rewrites history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The upside&lt;/strong&gt; — your history looks linear and clean.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;The downside&lt;/strong&gt; — if anyone else has those original commits (&lt;code&gt;A&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;C&lt;/code&gt;), their history now diverges from yours. This is what I did wrong.&lt;/p&gt;


&lt;h2&gt;
  
  
  The golden rule of rebase
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Never rebase commits that exist outside your local repository.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you've pushed a branch and someone else has pulled it, rebasing rewrites commits they already have. Their history diverges. Pulling becomes painful. You'll see duplicate commits, conflicts that make no sense, and a very confused colleague.&lt;/p&gt;

&lt;p&gt;The safe version — rebase only your local, unpushed work. Once commits are shared, merge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ✅ Safe — rebasing local work before pushing&lt;/span&gt;
git rebase main
git push origin feature/my-feature

&lt;span class="c"&gt;# ❌ Dangerous — rebasing already-pushed commits&lt;/span&gt;
git push origin feature/my-feature  &lt;span class="c"&gt;# already done&lt;/span&gt;
git rebase main                      &lt;span class="c"&gt;# rewrites shared history&lt;/span&gt;
git push &lt;span class="nt"&gt;--force&lt;/span&gt;                     &lt;span class="c"&gt;# now you've broken everyone else&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Interactive rebase
&lt;/h2&gt;

&lt;p&gt;Once you understand what rebase does, &lt;code&gt;git rebase -i&lt;/code&gt; becomes one of the most useful tools in Git.&lt;/p&gt;

&lt;p&gt;It lets you rewrite your local commit history before you share it — squash messy WIP commits into clean ones, reorder commits, edit commit messages, or split a large commit into smaller ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Rewrite the last 3 commits interactively&lt;/span&gt;
git rebase &lt;span class="nt"&gt;-i&lt;/span&gt; HEAD~3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens an editor where you choose what to do with each commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pick a1b2c3 Add order validation
pick d4e5f6 fix typo
pick g7h8i9 WIP: still debugging

&lt;span class="c"&gt;# Change to:&lt;/span&gt;
pick a1b2c3 Add order validation
squash d4e5f6 fix typo
drop g7h8i9 WIP: still debugging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result — one clean commit instead of three messy ones.&lt;/p&gt;

&lt;p&gt;This is what "clean history" actually means in practice — not avoiding merge commits, but being &lt;strong&gt;intentional about what you leave behind&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to use which
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use merge when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Integrating a completed feature branch into &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;develop&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Working on a shared branch where others have the same commits&lt;/li&gt;
&lt;li&gt;You want to preserve the full context of when and how branches diverged&lt;/li&gt;
&lt;li&gt;Merging release branches or hotfixes where an audit trail matters&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use rebase when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Updating your local feature branch with changes from &lt;code&gt;main&lt;/code&gt; before pushing&lt;/li&gt;
&lt;li&gt;Cleaning up local commit history before opening a PR (with &lt;code&gt;-i&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Keeping a long-lived feature branch up to date with the base branch&lt;/li&gt;
&lt;li&gt;Working solo on a branch nobody else has touched&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Never rebase:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Commits that have already been pushed and shared&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main&lt;/code&gt;, &lt;code&gt;develop&lt;/code&gt;, or any branch others are working from&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The workflow that actually works
&lt;/h2&gt;

&lt;p&gt;In practice, most teams land somewhere between "always merge" and "always rebase." Here's a workflow that's served me well:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Work locally on a feature branch&lt;/li&gt;
&lt;li&gt;Before opening a PR, &lt;code&gt;git rebase -i&lt;/code&gt; to clean up commits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git rebase main&lt;/code&gt; to update your branch with latest changes&lt;/li&gt;
&lt;li&gt;Push and open the PR&lt;/li&gt;
&lt;li&gt;Merge the PR into &lt;code&gt;main&lt;/code&gt; (with a merge commit or squash merge — team's call)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives you a clean PR history while preserving the merge commit that marks when a feature landed in &lt;code&gt;main&lt;/code&gt;. Best of both approaches.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I wish I had understood earlier
&lt;/h2&gt;

&lt;p&gt;Rebase and merge aren't competing philosophies. They're different tools for different moments in a workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merge preserves history. Rebase rewrites it.&lt;/strong&gt; Both are useful — but rewriting shared history is always a mistake.&lt;/p&gt;

&lt;p&gt;I learned that the hard way. The senior developer who taught me to "always rebase" wasn't wrong — he just assumed I understood what rebase does. I didn't. I copied the command without understanding the consequence.&lt;/p&gt;

&lt;p&gt;Now I understand both. And I still rebase — just never the wrong branch.&lt;/p&gt;




&lt;p&gt;How does your team handle this? Do you have a strict rebase-only policy, or do you let developers decide? I'm curious whether the "clean history" argument is still winning in 2026. 👇&lt;/p&gt;

</description>
      <category>git</category>
      <category>webdev</category>
      <category>programming</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>I Spent 3 Months Building a Game Nobody Understood. Here's What I Learned.</title>
      <dc:creator>Daniel Rusnok</dc:creator>
      <pubDate>Wed, 18 Mar 2026 16:08:45 +0000</pubDate>
      <link>https://forem.com/danielrusnok/i-spent-3-months-building-a-game-nobody-understood-heres-what-i-learned-24bh</link>
      <guid>https://forem.com/danielrusnok/i-spent-3-months-building-a-game-nobody-understood-heres-what-i-learned-24bh</guid>
      <description>&lt;p&gt;98 views. 40 plays. 1 comment — and that one was mine.&lt;/p&gt;

&lt;p&gt;That was the result of three months of work. Evenings, weekends, and a few sleepless nights. And the hardest part wasn't the numbers. It was the feedback that came with them: &lt;em&gt;"Interesting, but I'm not sure what it is."&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Actually 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.amazonaws.com%2Fuploads%2Farticles%2Fqqdh5eppubfx5kkmzw0p.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.amazonaws.com%2Fuploads%2Farticles%2Fqqdh5eppubfx5kkmzw0p.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Sappy was an emotional survival game — at least, that's what I told myself it was. The player controlled a small jester-like character moving through an open world, collecting orbs, and surviving.&lt;/p&gt;

&lt;p&gt;But Sappy wasn't just a jester. He had emotions. Real ones. When he was angry, his head turned red. Fear made him purple. Joy lit him up yellow. Sadness washed him in blue.&lt;/p&gt;

&lt;p&gt;Every single one of those states had its own hand-drawn sprites. I drew everything in Procreate — 4 animations, 4 directions each, across 4 emotional variants. That's a lot of sprites for one character. And I wasn't done.&lt;/p&gt;

&lt;p&gt;The environment had emotions too. Every tile, every tree, every background element existed in two versions: a &lt;em&gt;Sad&lt;/em&gt; version and a &lt;em&gt;Happy&lt;/em&gt; version. The world itself was supposed to breathe with Sappy's emotional state.&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fxj0t05r5fiuh67oajwp7.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.amazonaws.com%2Fuploads%2Farticles%2Fxj0t05r5fiuh67oajwp7.png" alt=" " width="800" height="287"&gt;&lt;/a&gt;&lt;br&gt;
I wanted to give the game a story. But it was an open-world map with survival mechanics. How do you give a narrative to something deliberately open? And if I added a story, did the survival mechanics still make sense? The more I thought about it, the less I knew what Sappy actually was.&lt;/p&gt;

&lt;p&gt;I kept building anyway.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're a developer navigating that strange intersection of code and creativity, I write a newsletter on Substack about exactly that — the solo dev journey, the mistakes, and what I'm figuring out along the way. You can find it at &lt;a href="https://danielrusnok.substack.com/" rel="noopener noreferrer"&gt;danielrusnok.substack.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Feedback That Broke the Illusion
&lt;/h2&gt;

&lt;p&gt;I shipped Sappy to &lt;a href="https://echoes-within-games.itch.io/sappy" rel="noopener noreferrer"&gt;itch.io&lt;/a&gt;. I reached out to gamedev communities and asked people to try it.&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fjc0nx061eiw1w9r6gu9e.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.amazonaws.com%2Fuploads%2Farticles%2Fjc0nx061eiw1w9r6gu9e.png" alt=" " width="800" height="803"&gt;&lt;/a&gt;&lt;br&gt;
The numbers were brutal. But numbers I could have accepted. What I couldn't shake was what people actually said.&lt;/p&gt;

&lt;p&gt;Nobody said the game was bad. They said they didn't know what it was.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Interesting, but I don't understand the goal."&lt;/em&gt; &lt;em&gt;"Cool character, but where's the onboarding?"&lt;/em&gt; &lt;em&gt;"What kind of game is this supposed to be?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These aren't the same as "this needs work." They're something worse. They mean the game had no identity — and no amount of polishing would fix that.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Wasn't the Work. It Was the Foundation.
&lt;/h2&gt;

&lt;p&gt;I'd been asking the wrong question the whole time.&lt;/p&gt;

&lt;p&gt;Instead of &lt;em&gt;"How do I build this better?"&lt;/em&gt; I should have been asking &lt;em&gt;"What is this, exactly?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Was Sappy an emotional survival game for adults? Then why was there a cheerful jester collecting orbs? Was it a children's game? Then why the abstract open-world mechanic with no clear goal? Was it a narrative game? Then the entire structure — open world, survival loop — was fighting the story rather than carrying it.&lt;/p&gt;

&lt;p&gt;I'd built hundreds of hand-drawn sprites. I'd coded the emotional state system. I'd designed a whole world with dual versions of every asset. And underneath all of it was a question I'd never answered: &lt;em&gt;What is this game for? Who is it for?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three months of evenings and sleepless nights — and I'd built something technically impressive that nobody, including me, could explain.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Five-Minute Scene That Changed Everything
&lt;/h2&gt;

&lt;p&gt;A few weeks after Sappy's launch, I wanted to play something that would actually move me. I asked ChatGPT to recommend a game — something emotionally driven, something I hadn't heard of.&lt;/p&gt;

&lt;p&gt;It suggested Planet of Lana.&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.amazonaws.com%2Fuploads%2Farticles%2Fvjj36i8lig2f4cfzv9xe.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.amazonaws.com%2Fuploads%2Farticles%2Fvjj36i8lig2f4cfzv9xe.jpg" alt="Discovered by Oreskis" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I didn't know it. I loaded it up.&lt;/p&gt;

&lt;p&gt;Within five minutes, two sisters are screaming at each other. No tutorial. No exposition dump. Just two characters, a relationship, and immediate emotional stakes.&lt;/p&gt;

&lt;p&gt;I paused the game and sat with that for a moment.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This. This is what I want to make.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Not survival loops. Not open worlds. A story you feel before you understand it. Two characters whose relationship is the mechanic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Starting Over (But Not From Zero)
&lt;/h2&gt;

&lt;p&gt;I didn't throw away Sappy. I finally understood what he was missing.&lt;/p&gt;

&lt;p&gt;The emotional state system — the red anger, the purple fear, the yellow joy, the blue sadness — that was real. That was worth keeping. But Sappy needed a world that made those emotions matter. He needed someone to have those emotions &lt;em&gt;with&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I started thinking about companionship. About what it means to navigate emotions not alone, but in relationship. I'd wanted to add a companion to Sappy from the beginning — I just hadn't known why. Now I did.&lt;/p&gt;

&lt;p&gt;I thought about who those two characters could be. And I found them in my own life: the person I was before I became a father, and the small person who changed me. A solo dev dad and the little girl who taught him to feel things he'd spent years ignoring.&lt;/p&gt;

&lt;p&gt;That's where Eli and Uri came from. You can follow the journey at &lt;a href="https://echoeswithingames.com" rel="noopener noreferrer"&gt;echoeswithingames.com&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.amazonaws.com%2Fuploads%2Farticles%2F579kdurdw7klvc19jzo8.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.amazonaws.com%2Fuploads%2Farticles%2F579kdurdw7klvc19jzo8.png" alt="Eli sketch" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A side-scrolling 2D game. Two characters. One emotional journey. A story that only I can tell — because it's mine.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Three Months of "Wasted" Work Actually Taught Me
&lt;/h2&gt;

&lt;p&gt;I didn't waste those three months. I just didn't know what I was learning yet.&lt;/p&gt;

&lt;p&gt;I learned that technical execution without creative clarity is a trap. You can draw hundreds of sprites and still not have a game. I learned that feedback saying &lt;em&gt;"I don't know what this is"&lt;/em&gt; is the most important feedback you'll ever get — and the most painful to hear. And I learned that the question &lt;em&gt;"What is this game, exactly?"&lt;/em&gt; has to be answered before anything else.&lt;/p&gt;

&lt;p&gt;Sappy taught me what I wanted to make. Sometimes that's worth three months and a hundred sleepless hours.&lt;/p&gt;

&lt;p&gt;If you're in the middle of a project that feels unclear — not broken, just undefined — stop building for a moment. Ask yourself the question I avoided for too long: &lt;em&gt;What is this, exactly? And who is it for?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You might not like the answer. But you'll build something better because of it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Are you a solo dev navigating the same questions? I'd love to hear about it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>indiedev</category>
      <category>solodev</category>
    </item>
    <item>
      <title>I've Been Slowing Down React for 3 Years. Here's What My Code Review Revealed.</title>
      <dc:creator>Daniel Rusnok</dc:creator>
      <pubDate>Tue, 17 Mar 2026 07:32:56 +0000</pubDate>
      <link>https://forem.com/danielrusnok/ive-been-slowing-down-react-for-3-years-heres-what-my-code-review-revealed-58ep</link>
      <guid>https://forem.com/danielrusnok/ive-been-slowing-down-react-for-3-years-heres-what-my-code-review-revealed-58ep</guid>
      <description>&lt;p&gt;I've been writing React for three years. I thought I understood memoization. Then a colleague reviewed my PR and pointed something out: I was wrapping almost every computed variable in &lt;code&gt;useMemo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Not because I had profiled anything. Not because there was a performance problem. Just because — why not? It felt responsible. He was polite about it. But the message was clear: I was making React do more work, not less.&lt;/p&gt;

&lt;p&gt;That conversation sent me down a rabbit hole I should have gone down years ago. Here's what I found.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cost of memoization
&lt;/h2&gt;

&lt;p&gt;The idea behind &lt;code&gt;useMemo&lt;/code&gt; is simple: cache the result of an expensive computation so React doesn't have to redo it on every render.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sortedList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of sorting on every render, React stores the result and only recalculates when &lt;code&gt;items&lt;/code&gt; changes. Sounds like a no-brainer, right?&lt;/p&gt;

&lt;p&gt;Here's the part I was ignoring: &lt;code&gt;useMemo&lt;/code&gt; itself has a cost. React stores the cached value, compares dependencies on every render, and manages the cache lifecycle.&lt;/p&gt;

&lt;p&gt;For simple computations — overkill.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This is almost certainly not worth memoizing&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// Just do this&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;String concatenation is done in microseconds. The memoization wrapper costs more than the computation. I had dozens of these in my codebase. And the worst part? I was proud of them. I thought I was being thorough.&lt;/p&gt;




&lt;h2&gt;
  
  
  Have a reason for useMemo
&lt;/h2&gt;

&lt;p&gt;Don't reach for &lt;code&gt;useMemo&lt;/code&gt; until you have a reason. A reason is either a measurable performance problem, or a referential stability requirement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Case 1: Expensive computation
&lt;/h3&gt;

&lt;p&gt;If the calculation is genuinely costly — filtering a large dataset, running a complex algorithm, processing a lot of data — memoization makes sense. The key word is &lt;em&gt;measurable&lt;/em&gt;. If you haven't profiled it, you don't know it's expensive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredProducts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;selectedCategory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;minPrice&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;maxPrice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rating&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="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectedCategory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;minPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxPrice&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Case 2: Referential stability
&lt;/h3&gt;

&lt;p&gt;This is the one most developers miss — and it's where &lt;code&gt;useMemo&lt;/code&gt; is actually most valuable.&lt;/p&gt;

&lt;p&gt;In JavaScript, &lt;code&gt;[] !== []&lt;/code&gt; and &lt;code&gt;{} !== {}&lt;/code&gt;. Two arrays or objects with identical contents are not the same reference. This matters because React's dependency comparison is based on reference equality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Without useMemo - new object reference on every render&lt;/span&gt;
&lt;span class="c1"&gt;// This will cause child components and useEffect to fire even if nothing changed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// With useMemo - stable reference, child only re-renders when values actually change&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&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;If you're passing an object or array as a prop to a memoized child component, or as a dependency to &lt;code&gt;useEffect&lt;/code&gt;, you almost certainly need &lt;code&gt;useMemo&lt;/code&gt; to maintain referential stability — not to save computation time.&lt;/p&gt;

&lt;p&gt;This was the one I genuinely didn't know. I'd been using &lt;code&gt;useMemo&lt;/code&gt; for the wrong reason — saving computation — and accidentally getting the right result sometimes. When I understood referential stability, a lot of confusing bugs from the past suddenly made sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  Don't interchange it with React.memo
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;React.memo&lt;/code&gt; wraps a component, not a value. It tells React: only re-render this component if its props have changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onSelect&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onSelect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;React.memo&lt;/code&gt;, &lt;code&gt;UserCard&lt;/code&gt; re-renders every time its parent re-renders — even if &lt;code&gt;user&lt;/code&gt; and &lt;code&gt;onSelect&lt;/code&gt; haven't changed. With it, React compares props before deciding whether to re-render.&lt;/p&gt;

&lt;p&gt;But here's the catch: &lt;code&gt;React.memo&lt;/code&gt; uses shallow comparison. If you pass a new object or function reference on every render — which happens by default — the memoization is useless.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This breaks React.memo - new function reference on every parent render&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserCard&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onSelect&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleSelect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// This works - stable reference via useCallback&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSelectUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleSelect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserCard&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onSelect&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSelectUser&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;React.memo&lt;/code&gt;, and &lt;code&gt;useCallback&lt;/code&gt; tend to appear together. They're solving the same underlying problem: preventing unnecessary work caused by reference instability.&lt;/p&gt;




&lt;h2&gt;
  
  
  useCallback is useMemo for functions
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;useCallback&lt;/code&gt; is essentially &lt;code&gt;useMemo&lt;/code&gt; for functions. It memoizes a function reference so it stays stable across renders.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Without useCallback - new function on every render&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;submitForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// With useCallback - same reference unless dependencies change&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;submitForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&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="nx"&gt;submitForm&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main use case: passing callbacks to memoized child components or including them as &lt;code&gt;useEffect&lt;/code&gt; dependencies. Without &lt;code&gt;useCallback&lt;/code&gt;, you'll break &lt;code&gt;React.memo&lt;/code&gt; optimization or trigger infinite &lt;code&gt;useEffect&lt;/code&gt; loops.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Classic infinite loop - handleFetch is a new reference on every render&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;handleFetch&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="nx"&gt;handleFetch&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// Fixed with useCallback&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleFetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&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="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;handleFetch&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="nx"&gt;handleFetch&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One rule of thumb: if you're writing &lt;code&gt;useCallback&lt;/code&gt; on a function that never gets passed anywhere as a prop or dependency, you probably don't need it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Profile before you optimize
&lt;/h2&gt;

&lt;p&gt;Before reaching for any of these tools, you need to know whether you have a problem. Guessing is how I ended up with memoization everywhere and performance that wasn't actually better.&lt;/p&gt;

&lt;p&gt;React DevTools Profiler is the right starting point. Record a user interaction, and it shows you exactly which components re-rendered, why, and how long they took. Look for components that render frequently with no visible reason.&lt;/p&gt;

&lt;p&gt;A few patterns that indicate real problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A component re-renders on every keystroke in a parent input, even though it doesn't use the input value&lt;/li&gt;
&lt;li&gt;A list re-renders all items when only one changes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useEffect&lt;/code&gt; fires more often than expected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you don't see these patterns — or if the render time is under a few milliseconds — memoization probably won't help. The cost of the abstraction outweighs the benefit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common mistakes that break memoization silently
&lt;/h2&gt;

&lt;p&gt;Even when you reach for these tools correctly, it's easy to misconfigure them and get none of the benefit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inline objects in JSX&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Breaks memoization - new object every render&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Chart&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Fixed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chartOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Chart&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;chartOptions&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Missing or incorrect dependencies&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Stale closure bug - reads outdated userId&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;api&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="s2"&gt;`/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt; &lt;span class="c1"&gt;// userId missing from deps&lt;/span&gt;

&lt;span class="c1"&gt;// Correct&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;api&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="s2"&gt;`/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Memoizing too high up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Wrapping an entire page component in &lt;code&gt;React.memo&lt;/code&gt; rarely helps — its props almost always change. Memoization is most effective on leaf components that receive stable props and render often.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision framework
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For &lt;code&gt;useMemo&lt;/code&gt;&lt;/strong&gt; — Is this computation expensive, or does reference stability matter? If neither — skip it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For &lt;code&gt;React.memo&lt;/code&gt;&lt;/strong&gt; — Does this component render often? Are its props actually stable? If props change frequently anyway, it adds cost without benefit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For &lt;code&gt;useCallback&lt;/code&gt;&lt;/strong&gt; — Is this function passed as a prop to a memoized component? Is it a dependency in &lt;code&gt;useEffect&lt;/code&gt;? If neither — skip it.&lt;/p&gt;




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

&lt;p&gt;The instinct to wrap everything in &lt;code&gt;useMemo&lt;/code&gt; came from a good place: I wanted to write performant code. But premature optimization isn't just a waste of time in React — it actively adds complexity: more dependencies to track, more places for stale closure bugs to hide, more cognitive overhead for anyone reading the code later.&lt;/p&gt;

&lt;p&gt;After that review, I went through the codebase and removed about 80% of the &lt;code&gt;useMemo&lt;/code&gt; calls I'd written. The app didn't slow down. The code got easier to read. That stung a little.&lt;/p&gt;

&lt;p&gt;My colleague's comment was a favor. I just didn't appreciate it until I understood why he was right.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a .NET and React developer sharing what I actually run into building apps at work. This article was originally published on &lt;a href="https://levelup.gitconnected.com/ive-been-slowing-down-react-for-3-years-heres-what-my-code-review-revealed" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Thought Code Comments Were Anti-Patterns. Then AI Changed My Mind.</title>
      <dc:creator>Daniel Rusnok</dc:creator>
      <pubDate>Sun, 15 Mar 2026 16:37:28 +0000</pubDate>
      <link>https://forem.com/danielrusnok/i-thought-code-comments-were-anti-patterns-then-ai-changed-my-mind-40j0</link>
      <guid>https://forem.com/danielrusnok/i-thought-code-comments-were-anti-patterns-then-ai-changed-my-mind-40j0</guid>
      <description>&lt;p&gt;A year ago, I took over a codebase I didn't write. It was vibe-coded — you could tell. Logic scattered across files, inconsistent naming, and comments. Everywhere.&lt;/p&gt;

&lt;p&gt;Above every method, inside every loop, explaining things the code was supposedly already saying.&lt;/p&gt;

&lt;p&gt;I rolled my eyes and started deleting them.&lt;/p&gt;

&lt;p&gt;Fast forward to today. I pair program with AI almost daily and I wonder — should AI-generated comments help me understand quickly generated code, or do they help the AI too?&lt;/p&gt;

&lt;h2&gt;
  
  
  Comments as anti-pattern
&lt;/h2&gt;

&lt;p&gt;Robert C. Martin was clear about it in Clean Code — a comment is a failure. If your code needs a comment to be understood, you didn't write it clearly enough.&lt;/p&gt;

&lt;p&gt;The goal is self-documenting code — names so expressive, structure so clean, that the intent is obvious without explanation.&lt;/p&gt;

&lt;p&gt;It's a principle that shaped a generation of developers. And for good reason. We've all seen the damage bad comments do:&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="c1"&gt;// increment i by 1&lt;/span&gt;
&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;

&lt;span class="c1"&gt;// get user&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These comments add noise, not signal. Worse, they rot. Code gets refactored, comments don't. Now you have documentation that actively misleads anyone reading it — including the AI tools that consume it.&lt;/p&gt;

&lt;p&gt;The traditional argument holds. Most comments are bad comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comments, comments everywhere
&lt;/h2&gt;

&lt;p&gt;The project had been built fast, by someone leaning heavily on AI generation. The comments weren't thoughtful — they were everywhere, auto-generated, verbose. Things like:&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="c1"&gt;// This method processes the order and saves it to the database&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ProcessOrderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Validate the order first&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ValidateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Save to database&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Publish event to notify other services&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_eventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PublishAsync&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;OrderProcessedEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&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;My instinct was — most of this is noise. The method name already says &lt;code&gt;ProcessOrderAsync&lt;/code&gt;. The variable names already tell you what's happening. The comments are just... the code, again, in English.&lt;/p&gt;

&lt;p&gt;I cleaned them up. And I felt good about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contextual comments make sense
&lt;/h2&gt;

&lt;p&gt;I use Claude daily now. And at some point I caught myself wondering: does AI actually perform better in a well-commented file? Not just more pleasant to work with — but measurably better suggestions?&lt;/p&gt;

&lt;p&gt;I started paying closer attention. Here's a concrete example that kept coming up. Two versions of the same method signature:&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="c1"&gt;// Version A - no comments&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Version B - one intent comment&lt;/span&gt;
&lt;span class="c1"&gt;// Validates business rules before persisting. Does NOT publish events —&lt;/span&gt;
&lt;span class="c1"&gt;// caller is responsible for dispatching domain events after success.&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateOrderCommand&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I ask AI to add error handling to Version B, it respects the constraint. It doesn't add event dispatching inside the method. With Version A, it sometimes does — because it doesn't know the rule.&lt;/p&gt;

&lt;p&gt;That comment isn't explaining what the code does. It's explaining &lt;em&gt;why&lt;/em&gt; it's structured the way it is, and what it deliberately doesn't do. That's information that can't live in a method name.&lt;/p&gt;

&lt;h2&gt;
  
  
  So who are comments actually for now?
&lt;/h2&gt;

&lt;p&gt;When Uncle Bob wrote Clean Code, the only reader of your comments was a human developer. The argument was: if you need a comment, you failed to communicate through code. Write better code instead.&lt;/p&gt;

&lt;p&gt;But today, your code has a second reader: the AI tools.&lt;/p&gt;

&lt;p&gt;You work with AI every day. And AI doesn't read code the way humans do. It reads context. It looks for signals about intent, constraints, and decisions that aren't visible in syntax.&lt;/p&gt;

&lt;p&gt;A well-placed comment isn't noise to an AI pair programmer — it's context it can actually use.&lt;/p&gt;

&lt;p&gt;Research backs this up from two directions. A 2024 study published at ACL, &lt;em&gt;Code Needs Comments: Enhancing Code LLMs with Comment Augmentation&lt;/em&gt;, found that LLMs trained and tested on commented code consistently outperformed those working with uncommented code. And a 2025 code review study tested GPT-4o and Gemini with and without contextual descriptions alongside code — the presence of context measurably changed the quality of the AI's output.&lt;/p&gt;

&lt;h2&gt;
  
  
  What vs. why
&lt;/h2&gt;

&lt;p&gt;After a year of this, my working rule has shifted — not reversed.&lt;/p&gt;

&lt;p&gt;Bad comments explain &lt;em&gt;what&lt;/em&gt; the code does. The code already does that. Good comments explain &lt;em&gt;why&lt;/em&gt; it does it that way, what constraints it's operating under, and what it deliberately avoids.&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="c1"&gt;// BAD - explains what, code already shows this&lt;/span&gt;
&lt;span class="c1"&gt;// Loop through orders and calculate total&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// GOOD - explains why, code can't show this&lt;/span&gt;
&lt;span class="c1"&gt;// We intentionally skip cancelled orders here.&lt;/span&gt;
&lt;span class="c1"&gt;// Cancelled amounts are handled separately in the reconciliation report&lt;/span&gt;
&lt;span class="c1"&gt;// to avoid double-counting with the refund pipeline.&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cancelled&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&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;The second comment survives refactoring. It carries information that no amount of clean naming can convey. And it gives your AI pair programmer the context it needs to not introduce a bug the next time it touches that loop.&lt;/p&gt;

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

&lt;p&gt;I still think most comments are anti-patterns. I still delete the ones that just narrate what the code does. I still believe that if you need three sentences to explain a method, the method probably needs to be broken up.&lt;/p&gt;

&lt;p&gt;But I've stopped treating all comments as a failure. Some comments are the only place to record a decision — a constraint that emerged from a bug, a rule from the business, a reason why the "obvious" refactor would break something in production.&lt;/p&gt;

&lt;p&gt;Those comments aren't admissions of defeat. They're institutional memory.&lt;/p&gt;

&lt;p&gt;And in an era where I'm handing chunks of my codebase to an AI and asking it to extend them, that memory matters more than it used to.&lt;/p&gt;

&lt;p&gt;The vibe-coded project I inherited? Still full of bad comments. But somewhere in that noise, there were a few that made the work easier. I wish I'd been more careful about which ones I deleted.&lt;/p&gt;




&lt;p&gt;Do you write comments for the AI, for your future self, or are they still mostly noise in your codebase? I'm genuinely curious where other developers have landed on this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Robert C. Martin, &lt;em&gt;Clean Code&lt;/em&gt; (2008)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aclanthology.org/" rel="noopener noreferrer"&gt;ACL 2024 — Code Needs Comments: Enhancing Code LLMs with Comment Augmentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/" rel="noopener noreferrer"&gt;arXiv 2025 — Evaluating Large Language Models for Code Review&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/copilot" rel="noopener noreferrer"&gt;GitHub Copilot documentation — context and completions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>ai</category>
      <category>codequality</category>
    </item>
    <item>
      <title>AI Is Quietly Erasing the Line Between Frontend and Backend Developers</title>
      <dc:creator>Daniel Rusnok</dc:creator>
      <pubDate>Wed, 11 Mar 2026 20:04:43 +0000</pubDate>
      <link>https://forem.com/danielrusnok/ai-is-quietly-erasing-the-line-between-frontend-and-backend-developers-26kp</link>
      <guid>https://forem.com/danielrusnok/ai-is-quietly-erasing-the-line-between-frontend-and-backend-developers-26kp</guid>
      <description>&lt;p&gt;Three months ago I switched from PC to a Mac. Last week I needed to start six projects at once — multiple frontend apps, a backend — and doing it manually in the terminal every single time was killing my flow.&lt;/p&gt;

&lt;p&gt;So I turned to Cursor:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Write me a shell script that starts everything with one command.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It worked. I didn't know the syntax. I didn't know the Mac conventions. I wouldn't have been able to write it alone. But in few minutes I had a tool that saved me tens of minutes every single day — built in an environment I barely knew.&lt;/p&gt;

&lt;p&gt;That moment made me think about something I'd been noticing across my team.&lt;/p&gt;

&lt;h2&gt;
  
  
  The walls getting thinner
&lt;/h2&gt;

&lt;p&gt;A backend developer on my team needed analytics dashboards. In the past, that meant writing a ticket, waiting for a frontend developer to pick it up, reviewing it, iterating.&lt;/p&gt;

&lt;p&gt;Instead, he spent a day with AI and shipped the dashboards himself. They weren't pixel-perfect. But they worked, they were accurate, and they unblocked the team immediately.&lt;/p&gt;

&lt;p&gt;A frontend developer needed a new property saved to the database. New endpoint, new field, migration. Normally a backend task. He knew the codebase well enough to understand the pattern — and AI helped him fill in the parts he didn't know. He didn't need to wait for anyone.&lt;/p&gt;

&lt;p&gt;And me, a .NET developer writing shell scripts on a Mac.&lt;/p&gt;

&lt;p&gt;None of us crossed into the other role's territory because we suddenly became experts. We crossed because AI lowered the barrier enough that our existing context — knowing the codebase, understanding the goal, recognizing what good looks like — was sufficient to get the job done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Genuinely exciting and a little dangerous
&lt;/h2&gt;

&lt;p&gt;The exciting part is obvious. Teams move faster. Individuals are less blocked. You don't need to interrupt a specialist for every small thing outside your primary domain. The cost of "I'll just do it myself" has dropped dramatically.&lt;/p&gt;

&lt;p&gt;But here's what worries me.&lt;/p&gt;

&lt;p&gt;When a frontend developer adds a backend endpoint, he's working in territory where he doesn't have the full mental map. He doesn't know which invariants the data model is protecting or which edge cases the original author thought through. Just happy path.&lt;/p&gt;

&lt;p&gt;The specialist would have caught the other paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the specialist's job harder?
&lt;/h2&gt;

&lt;p&gt;This is the part I think gets lost in the excitement about AI breaking down role barriers.&lt;/p&gt;

&lt;p&gt;When AI enables everyone to touch everything, the specialist — the person who actually owns a domain — needs to be more vigilant, not less.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More code is being written in your domain by people who don't fully understand it.&lt;/li&gt;
&lt;li&gt;More PRs are landing with logic that looks right but carries hidden assumptions.&lt;/li&gt;
&lt;li&gt;More edge cases are being handled confidently by someone who didn't know there was an edge case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're no longer only reviewing what your colleagues write. You're reviewing what their AI wrote for them in your territory.&lt;/p&gt;

&lt;p&gt;That's a different kind of review. Before, when a colleague added a backend endpoint, I could assume they'd at least seen the pattern before.&lt;/p&gt;

&lt;p&gt;Now I'm reviewing code written by someone who asked AI to fill in the parts they didn't know — and AI confidently filled them in.&lt;/p&gt;

&lt;p&gt;Now I have to think about those things explicitly, and I think that's harder than it sounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this leaves us
&lt;/h2&gt;

&lt;p&gt;AI is genuinely changing what individual developers can do.&lt;/p&gt;

&lt;p&gt;I'm not going back to manually starting projects in six terminal tabs.&lt;br&gt;
My backend colleague isn't going to stop building his own dashboards when he needs them.&lt;br&gt;
The productivity gains are real.&lt;/p&gt;

&lt;p&gt;But. Codebase isn't just a collection of working features. It's a system with assumptions, constraints, and accumulated decisions — and someone has to hold that knowledge. AI doesn't hold it. The person who wrote the ticket doesn't hold it. The specialist holds it.&lt;/p&gt;

&lt;p&gt;The barrier to contributing outside your role has dropped. The responsibility of owning your domain hasn't.&lt;/p&gt;

&lt;p&gt;If anything, it's grown.&lt;/p&gt;




&lt;p&gt;Are you seeing this in your team — people crossing role boundaries more often with AI? And how are you handling code review when AI is doing the crossing? Do you think the specialist has more work with code reviews?&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://survey.stackoverflow.co/2024/" rel="noopener noreferrer"&gt;Stack Overflow Developer Survey 2024&lt;/a&gt; — data on AI tool adoption and developer productivity across thousands of engineers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.blog/news-insights/research/research-quantifying-github-copilots-impact-on-developer-productivity-and-happiness/" rel="noopener noreferrer"&gt;Quantifying GitHub Copilot's impact on developer productivity&lt;/a&gt; — GitHub's own research on how AI assistance changes how fast developers ship&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://newsletter.pragmaticengineer.com/p/how-ai-changed-software-engineering" rel="noopener noreferrer"&gt;How AI Changed Software Engineering&lt;/a&gt; — Gergely Orosz on what's actually shifting in real engineering teams&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
    </item>
    <item>
      <title>3 TypeScript Features That Make C# Developers Jealous</title>
      <dc:creator>Daniel Rusnok</dc:creator>
      <pubDate>Mon, 09 Mar 2026 16:28:41 +0000</pubDate>
      <link>https://forem.com/danielrusnok/3-typescript-features-that-make-c-developers-jealous-4dmh</link>
      <guid>https://forem.com/danielrusnok/3-typescript-features-that-make-c-developers-jealous-4dmh</guid>
      <description>&lt;p&gt;&lt;em&gt;If you're coming from C#, these three TypeScript features will change how you think about types.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three years ago I was writing CQRS handlers in .NET. Today I'm building React components. TypeScript felt familiar — and then it felt weird.&lt;/p&gt;

&lt;p&gt;My first weeks on the frontend were humbling. A senior developer told me to stop using &lt;code&gt;let&lt;/code&gt; and always use &lt;code&gt;const&lt;/code&gt; — something about immutability that felt obvious to him and completely foreign to me. I was also regularly reminded that we had TypeScript and I kept writing plain JavaScript anyway. Old habits.&lt;/p&gt;

&lt;p&gt;But once I stopped fighting the ecosystem and started learning its rules, I found features I genuinely wish C# had. Here are three of them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Union Types with Literal Values
&lt;/h2&gt;

&lt;p&gt;In C#, when you need a property with a fixed set of string values, you reach for an enum. It works — but a string and an enum are two different things, and you're always mapping between them.&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="c1"&gt;// Doesn't work — string and enum are different types&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pending&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// You need explicit conversion&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pending&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Modern .NET helps with automatic conversion via attribute,&lt;/span&gt;
&lt;span class="c1"&gt;// but you still define the enum separately and add the plumbing:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonConverter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonStringEnumConverter&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;enum&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Pending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Processing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Completed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In TypeScript, a string literal is the type. No conversion, no attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OrderStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&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="s1"&gt;processing&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="s1"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I first reached for this with a Button component variant prop — &lt;code&gt;'primary' | 'secondary'&lt;/code&gt;. No separate file, no mapping. The type is the documentation, and the compiler enforces it immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Structural Typing
&lt;/h2&gt;

&lt;p&gt;In C#, a type is what it declares itself to be. If a method expects &lt;code&gt;IAnimal&lt;/code&gt;, the object must explicitly implement &lt;code&gt;IAnimal&lt;/code&gt;. The compiler checks names, not "shapes".&lt;/p&gt;

&lt;p&gt;TypeScript works the opposite way. If your object has the right properties, it is the right type — whether it says so or not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&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="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Daniel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My first instinct was to fight it. I added &lt;code&gt;as User&lt;/code&gt; everywhere, or used &lt;code&gt;satisfies User&lt;/code&gt; just so I could see the type and navigate the codebase by type references. Later, I got used to it and stopped adding types explicitly. The IDE watches type mismatches for me in real time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Utility Types
&lt;/h2&gt;

&lt;p&gt;C# developers are used to writing new types when they need a variation of an existing one. A DTO with fewer fields? New class. An update model with all optional fields? New class. A read-only projection? New class.&lt;/p&gt;

&lt;p&gt;TypeScript ships with a set of utility types that transform existing types instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ComponentProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onSave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Sub-component only needs part of the props&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;HeaderProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Pick&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ComponentProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&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="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Update form - everything optional&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UpdateProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ComponentProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Remove internal handlers from public API&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PublicProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Omit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ComponentProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onDelete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My favorite is &lt;code&gt;Pick&lt;/code&gt;, and I use it exactly like this in production. When a parent component has a large props interface, sub-components shouldn't receive everything — they work with a slice. &lt;code&gt;Pick&lt;/code&gt; lets you define that slice without creating a new type from scratch or reaching for inheritance.&lt;/p&gt;

&lt;p&gt;In C#, you'd either duplicate properties in a new class, use inheritance (which often brings unwanted baggage), or reach for libraries like AutoMapper to handle projections. TypeScript just gives you the tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus — "as const"
&lt;/h2&gt;

&lt;p&gt;In C#, you'd use &lt;code&gt;readonly&lt;/code&gt; or &lt;code&gt;const&lt;/code&gt; to prevent mutation. TypeScript has something more powerful: &lt;code&gt;as const&lt;/code&gt; freezes not just the value, but the type itself.&lt;/p&gt;

&lt;p&gt;Without &lt;code&gt;as const&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;directions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;north&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;south&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;east&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;west&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// TypeScript infers: string[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;as const&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;directions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;north&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;south&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;east&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;west&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// TypeScript infers: readonly ['north', 'south', 'east', 'west']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;directions&lt;/code&gt; isn't just an array of strings — it's a tuple of exactly those four literal values. You can even derive a type from it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;directions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// 'north' | 'south' | 'east' | 'west'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No separate type definition. No enum. The array is the source of truth, and the type follows automatically.&lt;/p&gt;




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

&lt;p&gt;Union types, structural typing, and utility types aren't just syntax sugar. They represent a different philosophy: types should describe your data, not govern your class hierarchy.&lt;/p&gt;

&lt;p&gt;Coming from C#, that shift takes time. But once it clicks, you start wishing the CLR worked the same way.&lt;/p&gt;

&lt;p&gt;If you're a .NET developer exploring TypeScript, what feature surprised you most? Let me know in the comments.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From .NET Architect to Frontend Developer — 3 Years on the Other Side</title>
      <dc:creator>Daniel Rusnok</dc:creator>
      <pubDate>Mon, 31 Mar 2025 04:54:49 +0000</pubDate>
      <link>https://forem.com/danielrusnok/from-net-architect-to-frontend-developer-3-years-on-the-other-side-1dgp</link>
      <guid>https://forem.com/danielrusnok/from-net-architect-to-frontend-developer-3-years-on-the-other-side-1dgp</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.amazonaws.com%2Fuploads%2Farticles%2Fdgghp1owoxa45ojccpiu.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.amazonaws.com%2Fuploads%2Farticles%2Fdgghp1owoxa45ojccpiu.png" alt="Image description" width="800" height="533"&gt;&lt;/a&gt;You might remember me from the days when I wrote about CQRS, DDD, and .NET.&lt;br&gt;&lt;br&gt;
Three years ago, I burned out at my previous job and looked for new opportunities. A change of company led to an unexpected opening on the frontend team.&lt;br&gt;&lt;br&gt;
I took the chance — and over time, transitioned from full-stack to fully frontend. My entire focus shifted.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the sudden change?
&lt;/h2&gt;

&lt;p&gt;I always wanted to build things that are more visible.&lt;br&gt;&lt;br&gt;
My creativity was getting thirstier, and backend work started to feel repetitive.&lt;/p&gt;

&lt;p&gt;In backend, I satisfied that creativity through architecture — designing service layers, models, and systems.&lt;br&gt;&lt;br&gt;
Now, it’s about layout, animations, and UX collaboration.&lt;/p&gt;




&lt;h2&gt;
  
  
  How do I feel about React?
&lt;/h2&gt;

&lt;p&gt;React clicked immediately. I love the component-based approach.&lt;br&gt;&lt;br&gt;
Finding the sweet spot between generic reusability and the Single Responsibility Principle is what drives me most.&lt;/p&gt;




&lt;h2&gt;
  
  
  What surprised me about frontend?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Progress
&lt;/h3&gt;

&lt;p&gt;Last time I touched frontend was 10 years ago — basic HTML, CSS, and jQuery.&lt;br&gt;&lt;br&gt;
Now I work on full apps, and I’m amazed how far the frontend has come. With React, TypeScript, Node.js, and Styled Components — it’s a whole different world.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tooling
&lt;/h3&gt;

&lt;p&gt;The tooling got so much better. Prettier and ESLint help keep things clean.&lt;br&gt;&lt;br&gt;
VS Code is lightweight and fast compared to classic Visual Studio — which improved, but still feels slow to me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bundle Size Awareness
&lt;/h3&gt;

&lt;p&gt;On backend, I could bring in any NuGet package without worrying about size.&lt;br&gt;&lt;br&gt;
In frontend, you really think twice before adding dependencies. We write a lot more ourselves to keep things lean.&lt;/p&gt;




&lt;h2&gt;
  
  
  What do I miss from backend?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clean Compiler Errors
&lt;/h3&gt;

&lt;p&gt;Could be just our internal setup, but debugging errors on frontend feels clunky.&lt;br&gt;&lt;br&gt;
VS gave me one-click navigation and clearer feedback.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit Testing
&lt;/h3&gt;

&lt;p&gt;Yes, Jest is there. But testing React components isn’t exactly joyful.&lt;br&gt;&lt;br&gt;
In design systems and libraries, sure. But in most day-to-day UI work? Meh.&lt;br&gt;&lt;br&gt;
We have QA engineers with Playwright doing automated testing — so I rarely miss it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Third-party Libraries
&lt;/h3&gt;

&lt;p&gt;Every lib must have a solid community and minimal bundle size.&lt;br&gt;&lt;br&gt;
You don’t just grab the first one — you evaluate first.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Had to Unlearn
&lt;/h2&gt;

&lt;h3&gt;
  
  
  OOP Principles
&lt;/h3&gt;

&lt;p&gt;I barely use classic OOP. Inheritance happens only in TypeScript interfaces and models.&lt;br&gt;&lt;br&gt;
Encapsulation? Only if the component lives in a single file.&lt;br&gt;&lt;br&gt;
Polymorphism? Maybe when passing &lt;code&gt;children&lt;/code&gt; into a React component and letting it take various forms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monolithic Thinking
&lt;/h3&gt;

&lt;p&gt;No more thinking in big blocks (modules, layers, orchestration).&lt;br&gt;&lt;br&gt;
Frontend taught me to think smaller — tiny components that fit like Lego.&lt;/p&gt;

&lt;h3&gt;
  
  
  Abstraction
&lt;/h3&gt;

&lt;p&gt;In backend, I’d sometimes spend hours building the perfect abstraction.&lt;br&gt;&lt;br&gt;
On frontend, I rarely feel the need — or the space — for that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;I’ve missed writing — and this post is my way back.&lt;/p&gt;

&lt;p&gt;I’d love to keep writing from the perspective of someone bridging backend and frontend — or a backend dev trying to do frontend well.&lt;/p&gt;

&lt;p&gt;Thanks for reading! If you’ve gone through something similar, I’d love to hear your experience.&lt;br&gt;
"&lt;/p&gt;

</description>
      <category>careerchange</category>
      <category>react</category>
      <category>dotnet</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Deep Dive into the Single Responsibility Principle</title>
      <dc:creator>Daniel Rusnok</dc:creator>
      <pubDate>Sun, 24 May 2020 15:29:07 +0000</pubDate>
      <link>https://forem.com/danielrusnok/deep-dive-into-the-single-responsibility-principle-24pn</link>
      <guid>https://forem.com/danielrusnok/deep-dive-into-the-single-responsibility-principle-24pn</guid>
      <description>&lt;p&gt;&lt;a href="https://medium.com/@danielrusnok/deep-dive-into-the-single-responsibility-principle-3726c5bf736c?source=friends_link&amp;amp;sk=3e39cacf0805bd4b4e05649dfddee894"&gt;Clean code&lt;/a&gt; &lt;em&gt;is a software development discipline which aims to raise IT company’s vitality by introducing practises, principles and patterns to improve developer’s programming skills. Programmer’s who follows&lt;/em&gt; Clean Code &lt;em&gt;becomes professionals and leaders in their fields.&lt;/em&gt;&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="https://medium.com/@danielrusnok/deep-dive-into-the-single-responsibility-principle-3726c5bf736c?source=friends_link&amp;amp;amp;sk=3e39cacf0805bd4b4e05649dfddee894" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W9dCrAIm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/fit/c/96/96/2%2Aw95yw399s0ZcKVgHoUEeXQ.jpeg" alt="Daniel Rusnok"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://medium.com/@danielrusnok/deep-dive-into-the-single-responsibility-principle-3726c5bf736c?source=friends_link&amp;amp;amp;sk=3e39cacf0805bd4b4e05649dfddee894" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Deep Dive into the Single Responsibility Principle - Daniel Rusnok - Medium&lt;/h2&gt;
      &lt;h3&gt;Daniel Rusnok ・ &lt;time&gt;May 24, 2020&lt;/time&gt; ・ 7 min read
      &lt;div class="ltag__link__servicename"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_EkM13RG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/medium_icon-2c57b3ec653e92a3d6207e708f1e4987099fc69342e556aaf9f035b1968b3f26.svg" alt="Medium Logo"&gt;
        Medium
      &lt;/div&gt;
    &lt;/h3&gt;
&lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Four Reasons to Change your software </title>
      <dc:creator>Daniel Rusnok</dc:creator>
      <pubDate>Sun, 17 May 2020 16:32:35 +0000</pubDate>
      <link>https://forem.com/danielrusnok/four-reasons-to-change-your-software-2j92</link>
      <guid>https://forem.com/danielrusnok/four-reasons-to-change-your-software-2j92</guid>
      <description>&lt;p&gt;&lt;a href="https://medium.com/@danielrusnok/four-reason-to-change-your-software-bf535916152d?sk=0dde468e6012d96c21bbd6700e23a66b"&gt;https://medium.com/@danielrusnok/four-reason-to-change-your-software-bf535916152d?sk=0dde468e6012d96c21bbd6700e23a66b&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to manage states with State Design Pattern in C#?</title>
      <dc:creator>Daniel Rusnok</dc:creator>
      <pubDate>Thu, 07 May 2020 06:23:16 +0000</pubDate>
      <link>https://forem.com/danielrusnok/how-to-manage-states-with-state-design-pattern-in-c-46di</link>
      <guid>https://forem.com/danielrusnok/how-to-manage-states-with-state-design-pattern-in-c-46di</guid>
      <description>&lt;p&gt;&lt;a href="https://link.medium.com/kfVfedrPh6"&gt;https://link.medium.com/kfVfedrPh6&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>design</category>
      <category>codequality</category>
    </item>
    <item>
      <title>How to use the Decorator Pattern in modern .NET applications?</title>
      <dc:creator>Daniel Rusnok</dc:creator>
      <pubDate>Tue, 28 Apr 2020 03:43:58 +0000</pubDate>
      <link>https://forem.com/danielrusnok/how-to-use-the-decorator-pattern-in-modern-net-applications-1i6n</link>
      <guid>https://forem.com/danielrusnok/how-to-use-the-decorator-pattern-in-modern-net-applications-1i6n</guid>
      <description>&lt;p&gt;&lt;a href="https://medium.com/@7467f0f889eb/b5c74a1b8ee?sk="&gt;https://medium.com/@7467f0f889eb/b5c74a1b8ee?sk=&lt;/a&gt; &lt;/p&gt;

</description>
      <category>design</category>
      <category>codequality</category>
      <category>codenewbie</category>
      <category>devops</category>
    </item>
    <item>
      <title>“Ugly dependency graph? The Mediator Design Pattern is the solution for you.</title>
      <dc:creator>Daniel Rusnok</dc:creator>
      <pubDate>Sun, 19 Apr 2020 16:41:52 +0000</pubDate>
      <link>https://forem.com/danielrusnok/ugly-dependency-graph-the-mediator-design-pattern-is-the-solution-for-you-2a98</link>
      <guid>https://forem.com/danielrusnok/ugly-dependency-graph-the-mediator-design-pattern-is-the-solution-for-you-2a98</guid>
      <description>&lt;p&gt;&lt;a href="https://link.medium.com/wMTFYs3FO5"&gt;https://link.medium.com/wMTFYs3FO5&lt;/a&gt;&lt;/p&gt;

</description>
      <category>development</category>
      <category>coding</category>
      <category>dotnet</category>
      <category>designpattern</category>
    </item>
  </channel>
</rss>
