My Journey at iCHEF: A 3-Year Review

Introduction

cover image

When I joined iCHEF, I set a personal goal: to make an impact and influence others without formal authority. This goal stemmed from my previous roles, where I was either a team lead or the most senior frontend engineer. I wanted to challenge myself to see if I could bring the same level of impact and influence in a larger team of 10+ frontend engineers, all while holding a relatively junior position compared to my peers.

Aside from my short-term goal, I’ve had a long-term goal that I set long ago and will carry throughout my career: to always embrace new challenges. Like most people, I work about 8 hours a day, and I want to stay excited about my work—well, at least for 30% of the time 😂. In this review, I’ll also reflect on my personal growth over the past three years.

Key Projects and Achievements

In this section, I’ll highlight some of my key achievements, along with a few major projects that I planned, initiated, and either executed or led. But before diving into the specifics, it's important to give you some context about iCHEF, because without understanding the company, any discussion of technical decisions would lack depth.

iCHEF specializes in providing POS systems, serving over 12,000 restaurants across Asia. Reliability is our top priority—if any part of our services goes down, even for a short time, it can affect a significant number of restaurants and their customers, potentially leading to substantial losses.

Improving Reliability

After surviving through several emergencies and hotfixes, I realized that every issue came from recent refactors. I also noticed that these bugs could have been prevented in advance, either with a different testing strategy or by using TypeScript (yeah, we were still using pure JavaScript at the time).

While I knew these changes would help, making the shift wasn’t easy because both solutions involved significant changes and added some extra workload. So, before introducing a new testing strategy and asking the team to write additional tests with new testing libraries, I started by discussing which types of tests we could safely ignore. We concluded that around 60% of our existing tests had minimal impact and weren’t worth maintaining. This groundwork allowed me to successfully introduce the new testing strategy and tools, which I rolled out through two internal workshops.

Speaking of TypeScript, back in 2020, before I joined, the team had already read the book Programming TypeScript: Making Your JavaScript Applications Scale and had decided against using it after conducting an assessment. So, when it came to introducing TypeScript, I took a different approach. Rather than holding more meetings to convince the team, I used every opportunity during tech meetings, design reviews, and incident postmortems to highlight potential benefits of TypeScript. It took about a year to get the team to reconsider, and the final push was when I took the led the creation of a 6-month training program to help the team feel comfortable using TypeScript.

Simplifying Things

"A good engineer is a lazy engineer." In engineering, laziness isn’t about avoiding work; it’s about avoiding unnecessary work. I like to think of myself as a lazy engineer, hoping to be a good one someday 😉.

The first thing I simplified was introducing a modern monorepo tool for my team. Before that, making changes to our shared libraries was far from enjoyable. We had to set up the development environment across multiple repos (we were using yarn link, and I often forgot to unlink, which led to unnecessary debugging time). When it came time to release those changes, we had to manually maintain the dependency chain, which was also a bit painful.

Another thing I simplified, and worth mentioning, is our i18n workflow. In the old workflow, we relied heavily on a localization platform to handle translations, version control, and branch merging. This tightly coupled our release process with the localization platform, and we had to maintain a strict 1-to-1 mapping with Git branches. The worst part was that the merging results were often non-deterministic. To address this, I implemented a policy where translations were committed directly to Git, and I wrote scripts to upload and download translations from a specific Git branch. As a result, our release workflow no longer needed to communicate with the localization platform, making our translations deterministic and allowing the translation team to fully leverage the platform to optimize their own workflow.

Mentorship Beyond Code

One of my mentees made the decision not to continue pursuing a career as a software engineer. How would you feel if a mentee with four years of experience, after a year and a half of mentorship, decided to take a completely different path?

Let’s call him Bob. This was Bob’s first job after graduating from a boot camp. When I became his mentor, we didn’t just stick to the usual routine—we spent a lot of time discussing what he wanted from his career, what would make him enjoy his work, and what kinds of opportunities would help him grow. We started by focusing on two main areas: learning how to break down complex problems and developing the essential skill for solving more advanced challenges.

Bob often felt frustrated trying to meet his own expectations. He began to realize that to reach his ideal career as a software engineer, he would need to put in even more time—sometimes more than his peers—which was burning him out and affecting his personal relationships. In the last few months, we started discussing whether a career change might be an option worth considering.

I’m genuinely happy that Bob finally made up his mind, even though I spent a lot of time mentoring him. I don’t see that time as wasted at all. I believe that if the opportunity arises in the future, Bob will recommend our team to friends who might be a great fit. And most importantly, I can see that Bob is excited about his new career path.

Aligning Priorities as a Team

How can we best use our resources to achieve the strategic outcomes we’re aiming for? Several strategies come to mind: recognizing our strengths and weaknesses, avoiding the reinvention of the wheel, narrowing our scope, and prioritizing our efforts where they matter most.

That’s why I initiated a technical review of our current tech stack. We revisited the reasons behind previous decisions and examined whether the conditions that led to them still apply. We also reassessed our architecture to ensure it remains flexible enough to support our organization’s goals over the next five years.

In the review meetings, we discussed 13 key topics and identified 54 action items. By prioritizing these tasks, the team can now focus on solving fewer but more significant and impactful problems each quarter, rather than tackling many small, disconnected issues. I’m excited to see how this process has strengthened our teamwork and allowed us to celebrate our successes together.

Design System

When the term "Design System" appears in blog posts, it’s common to see people encouraging others to build their own rather than choosing an existing one. We were no different—before I joined, the team had already been working on our own design system for a year.

It was during my second year at iCHEF that I realized this custom design system had become a bottleneck, slowing down the development of new features. I felt it was time to pause and seriously address the issue. So, I reached out to both my team and the design team to understand why we had decided to build everything from scratch, especially given that we didn’t have a dedicated design system expert.

The following year, after gathering feedback and conducting a through analysis, I managed to get both teams on board with finding an alternative design system that could cover at least 90% of our use cases. Luckily, Material UI checked all the boxes. It implements Google’s Material Design, offers a rich and consistent API, has recently solved the runtime performance issues of CSS-in-JS, and is easy to customize on both global and local levels. (I’ll dive deeper into this in my next post 🥸).

This major migration is set to kick off in 2025, and I’m really excited about the potential it holds. While the UI will largely remain the same, the shift will allow us to deliver new features much faster and more efficiently. I’m hopeful that this will shorten our product iteration cycles and lead to increased client satisfaction.

My Personal Growth

Over the past three years, I've found myself spending much less time writing JS/TS code. Instead, I've focused on a variety of other activities: writing in C and Rust, reading source code, books and documentation, building projects, taking classes, and doing some planning.

Convincing People with Data

I believe that using data to convince others is very essential. Without it, my goal of making an impact and influencing others without formal authority would be nearly impossible.

I’ve applied this skill extensively in both technical discussions and project planning. To me, it’s like analyzing complexity when choosing between different algorithms, but at an organizational level, where cost 💰 and time ⏳ are often the most significant variables.

I’m considering taking a course in data analysis and visualization to present data in a more professional and convincing way.

Building the Fundation

After spending years with JavaScript, I dedicated considerable time to learning Rust 🦀, aiming to deepen my understanding of programming by exploring a new language. Rust’s growing community, especially in the area of modern frontend tools, was another motivating factor. I highly recommend Jon Gjengset's Crust of Rust series and his book Rust for Rustaceans, both of which were instrumental in helping me bring my Rust skills to an intermediate level.

Due to the pandemic, many universities made their courses available online. I took advantage of this by completing Stanford's CS143, where I learned the fundamentals of compilers and built one for the COOL language from scratch. I also studied MIT's 6.S081, focusing on operating systems by working on xv6 through its labs. Additionally, the MIT's 6.006 course was updated in Spring 2020, so I revisited it and implemented the concepts in Rust to further practice the language.

To practice Rust even more, I began building toy versions of everyday software like BitTorrent, HTTP server, Docker, Git, and SQLite. Interestingly, I found myself reading a lot of documentation for these protocols, as CodeCrafters only provides instructions up to a certain point 😉.

Deeper Frontend Knowledge

I was lucky to find the course Bare Metal JavaScript: The JavaScript Virtual Machine right after wrapping up my compiler studies. Gaining a deeper understanding of monomorphism and inline caches really helped me appreciate the optimizations in TypeScript 5.0 and TypeScript 5.5. Microsoft’s Deopt Explorer made a lot more sense with this new perspective. For anyone interested, check out these great reads: Explaining JavaScript VMs in JavaScript - Inline Caches and What's up with monomorphism?.

Another area I explored was static analysis for dependencies. After managing imports and exports manually in my Dependency Tracker and exploring how wyw-in-js handles them, I’ve decided to avoid default export and wildcard export. I totally get why Google is not a fan of default export either. Plus, the new Isolated Declarations flag in TypeScript 5.5 does a pretty good job of explaining why I’m not crazy about wildcard export 🤯.

Understanding how Qwik achieves its impressive performance through resumability, closure serialization, and fine-grained reactivity took me a few months. I’m also planning to dive into Qwik’s optimizer, which is part of the reason I studied Rust, SWC, and compilers. And the micro-frontend architecture in Qwik? That’s something I’m particularly excited about!

Summary

  • Short-term goal: making an impact and influencing others without formal authority ✅
  • Long-term goal: always embracing new challenges ✅

I’d say, "You’ve done pretty well over these 3 years, Leo!" 🎉