Optimizing for Iteration Part 1: Deciding Your Stack

Jake Marsh
April 11, 2019

As we’ve discussed in a previous blog post, product development is hypothesis testing. This is especially true in the early stages of a company when you need to confirm or reject your hypothesis as quickly as possible. This process is then repeated until you (hopefully) reach product-market fit. To get there, your team needs to be able to work and build at a pace that allows for this constant and rapid iteration.

This is the start of a three-part series of our suggested engineering decisions and methodologies to maximize your team’s ability to iterate. From the tech stack, to how to organize your code, to daily best practices, we will go into detail on how we operate here at Monolist in order to quickly ship features our users love.

Monolist is a global inbox and intelligent assistant that helps you be the best you. Learn more or request access.

Part 1: Deciding Your Stack

Your company’s tech stack will be a fundamental part of how your engineering team works and operates. Your engineers should feel comfortable working in any part of the stack, especially when your team is small. This is why it’s important to optimize for the right things when choosing your technologies. Let’s dive into what we use here at Monolist and why.

Server: Ruby on Rails

Ruby on Rails is an incredibly popular web application framework first released in 2005. It uses, well, Ruby, as its programming language. Ruby is a fairly easy to read and write dynamic language. Coupled with Rails, web applications can be built and iterated upon easily. However, you do not have the safety and self-documentation of statically typed code.

Due to Rails’ age and popularity, it also has an incredibly strong community and ecosystem. Rails itself has optional modules for many of the common things your app will need to implement like an ORM, WebSockets or automated emails. We use it for all three. There is also a large number of other open source gems available and compatible with Rails.

Although the dynamic nature of Ruby can lead to some obvious errors, when paired with Rails they allow for quick and easy development of features across a large number of domains.

Client: Typescript, React

TypeScript is a superset of JavaScript that provides opt-in static type checking. As a long-time JavaScript developer, I will never go back to plain JavaScript. Static typing eliminates an entire class of bugs while also reducing the amount of test coverage necessary to achieve confidence. Defining your types and using TypeScript strictly also results in code that’s easy to parse and safer to modify. Large refactors of our core business logic, which are not uncommon, are no longer nerve wracking because we always know what data we’re working with and when. It also enables us to share the same types across all of our various TypeScript applications.

React is one of the most widely used JavaScript libraries, written and maintained by Facebook. It is second only to jQuery in popularity according to StackOverflow’s 2019 survey. It allows you to write TypeScript (or JavaScript) components in JSX, which is a syntax extension that looks and behaves similar to HTML. Although it seems odd at first, it is easy to pick up and quick to write. The popularity of React also means a huge community and ecosystem on which to build from. Thanks to a large number of React frameworks and even a native mobile application counterpart (more on that below), it is also very easy to read and write the same code across multiple platforms.


Next is a React framework from Zeit. It takes care of many things from server-side rendering to code splitting. It makes it extremely easy to build performant, isomorphic applications. Additionally, there’s a large community and plugin ecosystem that provides support and options as your app grows in complexity.

The earliest iterations of Monolist were built on a home-grown isomorphic framework. It was riddled with bugs and hard to debug. It made the developer experience quite painful, and we found ourselves wasting too much time fiddling with it and not enough time focusing on the product. We decided to go with Next for the reasons above, and even ended up getting some love from the Zeit CEO when he stumbled upon our Next-powered app.


Gatsby is a React framework for generating static sites. Although Next also has this capability (and Gatsby can do many of the same things as Next), we still prefer to use them both for different purposes. Since they both use React and support TypeScript, we’re still able to share any common modules between them.

Gatsby uses GraphQL to pull data from a variety of sources when generating your static files, such as an API or even local Markdown files. This makes it fairly versatile in what it can be used for. Our marketing site is powered by Gatsby, and we also use various community-supported plugins to manage our blog posts and job listings via simple Markdown files. This even allows non-engineers to update them fairly easily.

The end product of Gatsby is a static site generated from your React components. Since they’re static files, they load quickly and you don’t have to worry about any SEO implications around using a JavaScript framework. They can then be uploaded to a cloud provider such as Amazon S3 or Google Cloud Storage, which both support serving static sites. This also allows for essentially limitless scaling (at the cost of requests) if you’re lucky enough to receive any large traffic spikes.

Mobile: React Native

React Native is a very popular library also written and maintained by Facebook. It works with largely the same paradigms and modules as the browser-based React, making it an extremely strong candidate when considering the ability to write code once for all platforms. At Monolist, we share our internal business logic between web, iOS, and Android, allowing us to easily make any necessary changes across all three. We’ll talk more about that in Part 2.

React Native is not without its downsides, however. You are trading performance and some of the capabilities of a native application in order to write your app in JavaScript. If you try to make up for it by complementing it with native code, you can run into issues bridging between the two. Airbnb wrote a popular post about the difficulties they ran into and their decision to migrate away.

As your company grows it may indeed not remain the best option. But when your team is small, it allows you to work within familiar concepts while reusing large portions of your code. This enables any engineer on your team to iterate quickly on any platform.

What to Optimize For

We’ve optimized for three things as we’ve decided upon the various parts of our tech stack:

  • Easy to understand: look for languages or technologies that help ensure your codebase remains workable for both new and existing engineers. This could take the form of typed languages, well-known languages or frameworks, code sharing (DRY), or self-documenting code. Any time spent trying to debug, understand, or explain cryptic code is time wasted.
  • Plug and play: favor technologies that are easy to get up and running. This can mean a large ecosystem with libraries or plugins for your later needs, or just tools that require little to no initial setup. The goal is to minimize the time your team spends on non-feature engineering work.
  • Abstract away the hard parts: at the early stages of your company, you don’t have the time to spend focusing on smaller details like networking, SEO, or optimizing milliseconds off of your TTFB. Although they are “black boxes”, technologies that abstract these things away are your friend for now.

In Part 2 of this series we’ll cover some of the topics that we’ve touched upon here, including how to manage and organize your code for pain-free refactors, as well as maximizing shared code between your repos.

Want to give Monolist a try?

Just request access. Once you gain access, you'll be able to invite your friends and coworkers to skip the waitlist.

Follow us on Twitter