As a young company in an extremely competitive space with many engineering challenges, we strive to do our best to set ourselves up for success.

Check out our available roles at Careers at Picnic.

Our stack in a nutshell

Our development philosophy

Most of the decisions we make in terms of architecture and tooling are driven by whether they can allow us to make small experiments often, without fear of breaking existing code.

All of our code is written in TypeScript. We lean heavily on the type safety guarantees this provides, as well as the nice developer experience improvements like smart code completion and automatic renaming. We also heavily rely on io-ts for runtime type checking, mainly to ensure that any persisted data remains backwards compatible, and we share these types between the client and the server.

We strongly believe that organising our code in well-defined "shearing layers" allows us to focus our efforts, and constrains our technology choices: we're aiming to build a solid, stable core, surrounded by layers that will change more frequently (sometimes several times in a day!). For the core, we choose technologies that are "boring" and battle-tested, such as PostgreSQL and Redis, but we're not afraid to try experimental new libraries and approaches for the outermost layers, since these are likely to change soon anyway.

Our approach to testing follows this structure: our core is tested comprehensively with unit and integration tests, since it is critical to the functioning of the entire system. The outer layers (eg. most of our UI code) are mostly tested manually. We'd love to automate more of this, but since we're still a tiny team and our product is still changing rapidly, this will have to happen at a later stage.

App

We believe reactive programming is the most powerful approach for writing highly dynamic and stateful UI code.

Our app uses React Native, using functional components and hooks throughout the entire codebase. We use MobX for all of our UI state management—we feel it provides the benefits of functional reactive programming without the boilerplate or conceptual baggage of other state management libraries (we've used them all in past lives). We use RxJS in parts of the app where we need more control over the exact stream semantics.

Initial prototypes of the app used a GraphQL API built with Apollo, but we found that this wasn't a good match for our data model, and that a lot of the libraries in its ecosystem were very immature (especially related to caching and real-time updates). We have since replaced this with a custom API, using io-ts to provide the type safety guarantees that GraphQL provided. We use RSocket for pushing real-time updates to the app.

We rely heavily on SQLite to cache data on device. We initially tried various open source React Native libraries for data storage, but we found serious performance problems and bugs in all of them, so we decided to write our own minimal bindings to SQLite using React Native's new JSI architecture. This allows us to call C++ code directly from JavaScript, without having to go the React Native "bridge". This has massively improved our app performance, but as we're competing against some of the most well-engineered mobile apps in existence, we know we'll have to keep making performance a strong priority.