<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Mais</title><description>My online home</description><link>https://mais.codes/</link><language>en</language><item><title>Figuring Out Firebase Realtime Database Transactions in Node</title><link>https://mais.codes/posts/firebase-rtdb-transactions/</link><guid isPermaLink="true">https://mais.codes/posts/firebase-rtdb-transactions/</guid><description>Understanding Firebase RTDB transactions in Node and the confusing behaviors around null data, retries, and return values.</description><pubDate>Fri, 13 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few days ago, I was working on a small authentication feature using both Firebase Auth and the Realtime Database (RTDB). I decided to write this post because understanding how Firebase RTDB transactions work—especially in Node—took me longer than I expected, even after reading the docs.&lt;/p&gt;
&lt;p&gt;The scenario: a user is already logged in on their phone and enters a short code into another device to link their session.&lt;/p&gt;
&lt;p&gt;Since the authentication was already handled on the phone, our backend issued a Firebase Auth custom token tied to that code. The device where the code is entered would then sign in using this token.&lt;/p&gt;
&lt;p&gt;To securely bind the session in RTDB, we needed to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check if the session code was valid and unclaimed.&lt;/li&gt;
&lt;li&gt;Atomically assign the user ID to it.&lt;/li&gt;
&lt;li&gt;Ensure only one session claim succeeds.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The rest of this post dives into how these transactions work specifically in the Admin SDK for Node.js, and how to use them.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;The Confusion&lt;/h3&gt;
&lt;p&gt;Take this transaction example from &lt;a href=&quot;https://firebase.google.com/docs/database/admin/save-data#node.js_10&quot;&gt;the official docs&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const upvotesRef = db.ref(
  &apos;server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes&apos;
);
upvotesRef.transaction((current_value) =&amp;gt; {
  return (current_value || 0) + 1;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is straightforward: the callback returns a new value, and Firebase writes it.&lt;/p&gt;
&lt;p&gt;But when I tried running a similar transaction, the first call to the callback received &lt;code&gt;current_value&lt;/code&gt; as &lt;code&gt;null&lt;/code&gt;—even though a record already existed. My assumption was that I should have seen the record immediately.&lt;/p&gt;
&lt;p&gt;Reading further into the docs, I found this note:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Your transaction handler is called multiple times and must be able to handle null data. Even if there is existing data in your database it may not be locally cached when the transaction function is run.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So this behavior is expected: initially, &lt;code&gt;current_value&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt; because it&apos;s coming from the local cache. Firebase will then fetch the latest state from the server and re-run the callback with fresh data.&lt;/p&gt;
&lt;p&gt;Here was my initial transaction logic:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const result = await codeRef.transaction((currentData) =&amp;gt; {
  if (currentData === null) {
    return null; // Will delete node if data is truly null
  }

  const code = Code.fromData(currentData);
 
  if (code.isExpired()) {
    return; // Abort transaction, code is expired
  }

  return code.use(uid);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a few possible paths here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The code exists — we fetch it and apply the update if it&apos;s valid.&lt;/li&gt;
&lt;li&gt;The code doesn&apos;t exist — &lt;code&gt;currentData&lt;/code&gt; comes as &lt;code&gt;null&lt;/code&gt; from the server.&lt;/li&gt;
&lt;li&gt;The code exists but is expired — we abort the transaction.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the first case, the initial state is &lt;code&gt;null&lt;/code&gt;, triggering the first &lt;code&gt;if-block&lt;/code&gt; and returning &lt;code&gt;null&lt;/code&gt;. Firebase automatically retries with the fresh server state, and the logic proceeds.&lt;/p&gt;
&lt;p&gt;In the second case, the first run returns &lt;code&gt;null&lt;/code&gt;, and when Firebase syncs with the server and still finds nothing, the transaction commits the &lt;code&gt;null&lt;/code&gt; (effectively a no-op).&lt;/p&gt;
&lt;p&gt;In the third case, the same initial &lt;code&gt;null&lt;/code&gt; happens, but the second run receives the real data, sees that the code is expired, and aborts.&lt;/p&gt;
&lt;p&gt;So what&apos;s the confusion?&lt;/p&gt;
&lt;p&gt;It was this: what&apos;s the difference between returning &lt;code&gt;null&lt;/code&gt; vs. &lt;code&gt;undefined&lt;/code&gt;? Why does one retry and the other doesn&apos;t? What exactly happens under the hood?&lt;/p&gt;
&lt;h3&gt;Going a Bit Deeper&lt;/h3&gt;
&lt;p&gt;Understanding how Firebase handles transactions internally clears things up.&lt;/p&gt;
&lt;p&gt;In RTDB, the transaction callback may first receive stale or incomplete local data—often just &lt;code&gt;null&lt;/code&gt;. Firebase uses optimistic concurrency control: it starts the transaction with local cache data (which may be stale), then automatically retries with fresh server data if needed.&lt;/p&gt;
&lt;p&gt;Here&apos;s how the return values work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Returning undefined&lt;/strong&gt; means: &quot;Abort the transaction.&quot;&lt;br /&gt;
Firebase immediately exits with committed = false, no retries.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Returning any other value&lt;/strong&gt; (including &lt;code&gt;null&lt;/code&gt;) means: &quot;Commit this value.&quot;&lt;br /&gt;
If you return &lt;code&gt;null&lt;/code&gt;, you&apos;re telling Firebase to set the node to &lt;code&gt;null&lt;/code&gt; (i.e., delete it).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The key insight: retries are driven by Firebase&apos;s optimistic concurrency model, not by what you return.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Back to our code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (currentData === null) {
  return null; // This will delete the node if data is truly null
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If the local cache is stale and &lt;code&gt;currentData&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt;, Firebase automatically retries with fresh server data&lt;/li&gt;
&lt;li&gt;If the server data is also &lt;code&gt;null&lt;/code&gt; (record doesn&apos;t exist), Firebase commits the &lt;code&gt;null&lt;/code&gt; value (no-op for non-existent data)&lt;/li&gt;
&lt;li&gt;The retry mechanism is built into Firebase&apos;s transaction system, not triggered by returning &lt;code&gt;null&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;No Transactions, Maybe?&lt;/h3&gt;
&lt;p&gt;After digging into the SDK and internal behavior, I wondered if a simpler design would work.&lt;br /&gt;
In my case, I just wanted to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check if a record exists&lt;/li&gt;
&lt;li&gt;Read it&lt;/li&gt;
&lt;li&gt;Possibly update it&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So I tried replacing the transaction with a regular read and conditional write.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const snapshot = await codeRef.once(&apos;value&apos;);

if (!snapshot.exists()) {
  throw new Error(&apos;Code does not exist&apos;);
}

const code = Code.fromData(snapshot.val());

if (code.isExpired()) {
  throw new Error(&apos;Code expired&apos;);
}

await codeRef.set(code.use(uid));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses 1 read (via &lt;code&gt;.once()&lt;/code&gt; — which always goes to the server)&lt;/li&gt;
&lt;li&gt;Writes only if needed&lt;/li&gt;
&lt;li&gt;Avoids the retry logic and potential edge cases with transactions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&apos;s simpler and faster if concurrent modifications aren&apos;t a risk — for example, if I&apos;ve enforced uniqueness at a higher level (like preventing the same code from being used twice). But I hadn&apos;t done that. Multiple clients could try to use the same code at once. In that case, the transaction is still the safer and more efficient choice — because it gives atomicity out of the box.&lt;/p&gt;
&lt;p&gt;And since RTDB charges by data transferred, not by operation count, the number of reads/writes doesn&apos;t matter as much — what I want to minimize is bandwidth, not function calls.&lt;/p&gt;
&lt;p&gt;Just thought I&apos;d share this — since getting my head around how Firebase handles transactions took me a while. Hope this saves someone else some time :)&lt;/p&gt;
</content:encoded></item><item><title>Growing as a software engineer</title><link>https://mais.codes/posts/growing-as-software-engineer/</link><guid isPermaLink="true">https://mais.codes/posts/growing-as-software-engineer/</guid><description>I&apos;m proficient in more than 2 programming languages, I built many apps, but what&apos;s next?</description><pubDate>Sat, 24 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Many software engineers face a common dilemma at some point in their careers: a feeling of stagnation or lack of growth.
I will discuss what I think is helping me go over this. It&apos;s not based on science or anything, just my experience.&lt;/p&gt;
&lt;p&gt;Prior to starting my first job, I was still a fresh grad, at that time I joined a couple of local and global communities, and this allowed me to be exposed to a set of skills I wouldn&apos;t normally have if I just got into a job. Being part of a community, organizing and hosting meetups, speaking and preparing content, leading a team, speaking to sponsors, dealing with different personalities... etc, and many other things I didn&apos;t imagine would be required in order to grow a local community group!&lt;/p&gt;
&lt;p&gt;As one of the early organizers of a GDG (Google Developer Group) in Saudi Arabia, I learned firsthand the importance of leadership. Our goal was to establish GDGs in major cities, and that required leaders who could inspire and motivate others. The key trait shared by successful leaders in our community was their &lt;strong&gt;initiative&lt;/strong&gt; and &lt;strong&gt;leadership&lt;/strong&gt; skills.&lt;/p&gt;
&lt;p&gt;Becoming a leader early in my career boosted my confidence and resilience. It gave me the courage to express my opinions in meetings with experienced professionals. It also teached me the importance of being reselliant in overcoming challenges, hard problems and human conflicts within the workplace.&lt;/p&gt;
&lt;p&gt;Many people underestimate the value of activities like building communities or public speaking. They might argue, &quot;What&apos;s the immediate return? I&apos;m not being paid to spend that time. I&apos;d rather learn a new language or tool.&quot; However, these activities are long-term investments in your career. Just like financial investments, the immediate return may not be significant, but over time, the value can grow exponentially. I personally view everything I do &quot;for free&quot; as an investment in myself, my social skills, my network, and more.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s talk about the growth you&apos;re probably looking for: becoming a senior software engineer. Reaching seniority isn&apos;t solely measured by the number of tools you master or projects you&apos;ve completed. While technical experience is important, the soft skills that differentiate developers are often overlooked.&lt;/p&gt;
&lt;h2&gt;Who is a &quot;senior&quot; in a team environment?&lt;/h2&gt;
&lt;p&gt;A senior isn&apos;t necessarily the oldest or most technically skilled person. Instead, they are the individual with strong leadership and influence. If you&apos;re stuck, a senior should be able to help you, not by solving the problem directly, but by guiding you in the right direction, inspiring new ideas, and pushing you forward. A good senior won&apos;t make you feel bad for not being able to accomplish your assigned task.&lt;/p&gt;
&lt;p&gt;When a team faces blockers or deadlines, a senior can identify the root cause, reallocate team members, create an action plan, and mitigate risks. These skills are not achieved by isolating yourself.&lt;/p&gt;
&lt;h2&gt;Having a depth while maintaing breadth&lt;/h2&gt;
&lt;p&gt;Since I started the first half of my career as a Flutter developer, I will tell this section from that angle, however it applies to everyone no matter the niche you choose. I learned Flutter and got my first job in it, and most of my contributions publicly were related to Flutter. While I admire this tool, and have reached a sufficient depth in it, what about my breadth?&lt;/p&gt;
&lt;p&gt;After 2 years of doing mostly Flutter and mobile development, I started venturing into other domains and technologies such as web development, cloud, and even machine learning. This exploration wasn&apos;t just about adding new tools to my toolkit; it was about expanding my perspective and understanding how these different domains connect and overlap.&lt;/p&gt;
&lt;p&gt;Maintaining a balance between depth and breadth is essential for long-term growth as a software engineer. In essence, as you grow in your career, don’t be afraid to venture outside your comfort zone. Embrace new technologies, explore different domains, and always be on the lookout for how you can integrate these new learnings into your existing skill set. This combination of depth and breadth will make you a more well-rounded, resilient, and innovative engineer.&lt;/p&gt;
&lt;h2&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;Your path will be unique, and while I&apos;ve covered key aspects, remember that individual experiences and career paths vary. My final advice is to invest in both technical skills and soft skills. The benefits of strong interpersonal skills, communication abilities, and leadership qualities are often underestimated but can significantly impact your career trajectory.&lt;/p&gt;
</content:encoded></item><item><title>My journey as graduate student with a full-time job</title><link>https://mais.codes/posts/graduate-journey/</link><guid isPermaLink="true">https://mais.codes/posts/graduate-journey/</guid><description>Sharing my journey from a fresh graduate to a lead software engineer pursuing a PhD, offering an honest reflection on the challenges of balancing work, life, and studies.</description><pubDate>Sat, 17 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The beginning&lt;/h2&gt;
&lt;p&gt;In 2019, I graduated with a Bachelor&apos;s degree in IT, enthusiastic about the future. However, I was not sure what I wanted to do next.
During my graduation project I learnt Flutter and Firebase, Flutter was still relatively new back then, but thanks to its fantastic documentation and supportive community, I was able to pick it up quickly and build a mobile app for my project.&lt;/p&gt;
&lt;p&gt;After graduation, I started applying here and there, looking for any opportunity that would help me grow. Unfortunately, I was not able to find a job, so I started looking for something to do on my own that would help me grow and learn new things. Well... that was actually harder than I thought. For the next year and a half, I stayed unemployed, slowly losing hope and motivation.&lt;/p&gt;
&lt;p&gt;Eventually, I did find a job, then another, but that&apos;s a story for another post. Today, I&apos;m working remotely as a lead software engineer and pursuing my PhD. This brings me to the reason I&apos;m writing this: to share my personal experience managing a full-time job and graduate studies.&lt;/p&gt;
&lt;h2&gt;Work-life-study balance...&lt;/h2&gt;
&lt;p&gt;So, did I find that elusive balance? The short answer is no. If you&apos;re searching for the magic formula, I&apos;m afraid I don&apos;t have it. I&apos;m writing this simply to share my journey. There might be a nugget or two of advice hidden within, but proceed at your own risk.&lt;/p&gt;
&lt;p&gt;When I first started my current job, going back to school wasn&apos;t even on my radar. But after a year, I decided to apply to some universities in Riyadh, and surprisingly, I got accepted! This threw me for a loop. Should I do it? What would it cost me? Eventually, I decided to go for a Master&apos;s in Data Science, all while keeping my full-time job. The program&apos;s flexibility was a huge help, with classes starting late in the afternoon. My company was really supportive, and working from home saved me a lot of time, and honestly, my father&apos;s confidence in me and his constant support were the biggest reasons I went for it.&lt;/p&gt;
&lt;p&gt;Truth be told, I barely studied. It was usually only a few days before exams that I even remembered what courses I was taking. This worked for me because of the years I&apos;d spent in the industry (only 3, btw), mastering languages like Python and Java – skills that were directly relevant to my coursework. My real-world experience made the academic side feel much easier, and made more sense. So, my honest advice: if you&apos;re thinking about jumping straight from your bachelor&apos;s into a master&apos;s program, don&apos;t. Get out there and work. Figure out what you enjoy, learn as many languages as you can, master the fundamentals, and work on real projects. Then, and only then, should you consider graduate studies with clear goals in mind.&lt;/p&gt;
&lt;p&gt;I graduated a few months ago, and the only truly stressful period was during my thesis. It coincided with some major work deadlines, and let&apos;s just say I&apos;m not the best at time management. My procrastination habit put me under immense pressure from both sides. Procrastination... it&apos;s my biggest enemy, yet it&apos;s also something I&apos;ve come to accept. My brain seems to function best under pressure, as unfortunate as that may be. If you&apos;re curious about what kept me so busy, &lt;a href=&quot;https://aclanthology.org/2024.arabicnlp-1.78/&quot;&gt;here&apos;s a link to my research project&lt;/a&gt; – it was just published! 🎉&lt;/p&gt;
&lt;p&gt;This video hits close to home – it&apos;s hilarious but also painfully accurate. If any of the points mentioned resonate with you, you&apos;ll definitely get a laugh (and maybe a bit of self-reflection) out of it.&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot;  src=&quot;https://www.youtube.com/embed/arj7oStGLkU?si=B002RGefmSKaKBL-&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;h2&gt;Why PhD?&lt;/h2&gt;
&lt;p&gt;My interest in Machine Learning led me to pursue a master&apos;s degree. It&apos;s a relatively new and exciting field with lots of research happening, and I wanted to be part of it, to contribute to science in some way, even if it&apos;s just a little bit.&lt;/p&gt;
&lt;p&gt;So here I am today, starting my PhD journey in just a few weeks! I know this post was a bit all over the place, but I hope you enjoyed reading it nonetheless.&lt;/p&gt;
</content:encoded></item></channel></rss>