With the year coming to an end, I’ve been reflecting on what has gone well and what can be improved. Naturally, this being the first year I’ve been full-time on my own project, a lot of that reflection has centered around Chessbook. So here’s my relatively unfiltered thoughts on what went well and what didn’t go so well.

Personal productivity

3/5

Maintaining my personal productivity is a constant battle. I had some really great stretches this year, but especially in the latter third it’s been a struggle.

Prioritization

3/5

Running a business means you always have a million things to do, and can do at most a few on any given day. It sometimes feels like 90% of success is about picking which 1% you do.

I have a hard time correctly prioritizing tasks. I’m naturally very biased towards the things I like working on. Yes this is true of every human ever, but I think even moreso for me. I’ll put off something like fixing iOS notifications, even though that drives a lot of retention, because dealing with that can be a huge pain. And at least that’s still programming. Stuff like filing taxes, or verifying with Stripe, or getting a DUNS number, etc can take weeks or months. I’ve gotten in the habit of pretending to be a Chessbook PM at the beginning of the day, and picking my tasks then, to have a shot of not just working on the fun stuff.

I do pretty well, all things considered. I think it’s not uncommon among founders to just totally fail at this, so I’m happy with where I’m at. My cofounder is a big piece of this too, as he’ll push me on stuff that I’m avoiding, when needed.

Putting in the hours

4/5

As a corollary to the above, where 90% of success is picking what to work on, the other 90% is putting in the hours.

There were some days where I barely did anything, but I also had very few zero-days, and some days I worked from 8am to 2am. That’s not a recommendation, this wildly swinging productivity is just how I work. What I’ve found to work best is to set boundaries around my time, and make sure that I’m available to plug in when the mood takes me.

I did get into a bit of a slump in the latter third of the year. I got to a point with Chessbook where I was finally earning enough of a salary to not have to actively worry about going bankrupt. Once I achieved that, I think all the moonlighting, the long hours, the stress of burning through savings, etc, sort of caught up to me all at once. I took a couple weeks totally off, and then it took a little time to pick up steam again.

Some Life Stuff™ got in the way too, but nothing that I could have avoided, so I can’t really dock myself for that. I have a wife now, so I suppose that’s worth some days lost of productivity.

As an incredibly rough measure of putting in the hours, that’s more interesting than useful, I’ve written 15,000 lines of Rust, 9,000 lines of typescript, and 1,000 lines of SQL. That’s about 70 lines per day on average.

Tech choices

5/5

I think I’ve killed it with the tech choices this year, and have continued to benefit from the previous choices I’m now tied to.

SolidJS

5/5

SolidJS is still one of the best decisions I’ve ever made. It’s not a stretch to say it has made me twice as productive on the frontend.

There’s two big pieces here. The first is the state management. It’s like someone read my mind and created exactly what I always wanted out of frontend state management. Use plain old javascript objects, nest freely, subscribe to any piece of it from any component, and mutate it just like a regular object – no funky immutable updates like Redux. And there’s no performance penalty anywhere!

Speaking of performance, I have never1 had to think about performance since switching from React to SolidJS. There used to be all sorts of funky stuff I had to do around memo-izing, splitting up components, controlled updates, etc etc, in order to make the chessboard update reasonably quickly. I migrated to SolidJS, and not only were updates to the chessboard 3x faster, but I could delete all the performance optimization cruft that just added incidental complexity. Battery life is also better as a consequence – even if your updates are <16ms for 60fps, there’s a big difference in battery life between 12ms updates and 2ms updates. People can spend an hour+ reviewing their moves, and we don’t want to rapidly drain battery during that time. This is just the chessboard, but this applies to every component on the site.

Rust

4/5

I’m torn on this one. There’s probably no better language I could have used for this project, so in some sense it should be an inarguable 5. But there’s some drawbacks with it that have really bitten me.

Compile times have been a real problem. I can tab away and skim through the front page of Hacker News, before my incremental build finishes compiling. This is a huge drag on my productivity, and seems sort of crazy for a 30k LOC codebase. It’s pretty opaque what’s going on here, as Rust doesn’t provide great debugging for this sort of stuff. One option is to split up my crate into a bunch of smaller ones. And I don’t even know if I’d be cutting it up in the right way to get faster compile times, because of the aforementioned lack of observability.

I’ve also hit a few bugs that have caused me a great amount of grief. These bugs are my fault, but Rust has made them hard to debug. One was a memory leak which I couldn’t figure out for months. It seemed to only happen in production, and would take down the server every 6 hours or so. Restarts were fast so uptime was still high, but it was a huge pain. Other languages I could have used, like Java, Python, JavaScript, etc, have much better tools to figure out this sort of stuff – in Rust I found it very hard to inspect what was going on. Recently I’ve been seeing an issue where threads get pegged, which reduces the throughput of my server and increases p99 latency. This sort of has the same shape as the first problem, where Rust being a compiled language with no JVM makes it quite hard to figure out where things are going wrong.

Rust is still a hell of a language, and I generally love working in it, but it doesn’t feel like a 10/10 slam dunk recently.

CapacitorJS

5/5

For those that don’t know, CapacitorJS is a framework for packaging your web app as a native app. Yeah yeah, I know. But look, I’m not about to write a Swift app, a Kotlin app, and a JS app. CapacitorJS has been awesome. I don’t even test mobile before launching it now, unless I’ve written native-specific code. If web works, native will work too. This was not true of React Native.

The native apps have been a huge win – they make up about half of our revenue, they get a lot of organic traffic, and people convert much more often. They have an average rating of 4.9 on both the Play Store and App Store. Getting all that for free, has been an absolute win.

I have had to write a few native plugins – for playing sounds, saving files, and checking if audio is playing. This process could be streamlined a tiny bit, but overall has been nice.

Fastlane

4/5

Fastlane was a new addition this year. It releases your apps for you. It took a bit of configuring, and it breaks fairly often if you don’t keep up on updating it, but I can’t blame it for trying to keep up with Apple’s APIs that change all the time. Releasing iOS and Android is a one-liner now, and that means mobile users don’t get outdated builds as often. Clicking through all the buttons on XCode + Android Studio + App Store Connect + Play Store was a huge ugh field before setting up Fastlane.

Posthog

3/5

Posthog is a product analytics platform. All the pieces are there, but there’s been a fair amount of bugs we’ve had to file, it can be unstable at times, identifying users has had a few footguns, and I don’t love the UI.

A/B tests are great, and it’s much cheaper than Amplitude (our previous analytics provider), but overall I’m not super happy with it. That being said, it may be the best product analytics tool out there. I haven’t seen anything that does everything it does, and that seems more promising.

Honeycomb

4/5

Honeycomb is an observability platform. I think I tried to hook up like 3 different observability platforms until I found honeycomb which actually worked. Even then it was sort of tough. I got the impression that the OpenTelemetry story is sort of early days (maybe specifically in Rust), or maybe the problem is me.

Anyway once I got it hooked up, this was a huge win. I don’t use it much, but when I do it’s invaluable. I can see things that I just can’t figure out by looking through logs. I found some really easy wins on performance when I started using this, and also fixed some bugs I’d had trouble figuring out for a long time. Great tool.

It is expensive though. I’d love to trace all my requests, but it would be >$1000/mo, and I can’t justify that. There’s a real qualitative difference between 20% of requests being traced, and 100% – in the latter case you can look at any suspicious request you see in the network tab, and go find the trace.

FSRS

5/5

FSRS is Anki’s new spaced repetition system, and is openly available for other applications to use. Swapping this in, to replace our hand-rolled supermemo-ish algorithm was an easy win. This project deserves a lot more love, and should be used by virtually every educational platform in my opinion. These guys have made a really nice wheel here, don’t reinvent this one.

Product direction

4/5

The headline features this year have been model games, an openings report, repertoire statistics, pre-made repertoires, and streaks.

Pre-made repertoires: I think I over-indexed on this one. They’re certainly popular, but weren’t the massive win I thought they’d be. There was also a ton of complexity hidden under the surface. It’s really easy to just merge in a static repertoire, but we went the full mile of only importing the moves that are relevant at your rating range, being able to browse those moves ahead-of-time, choosing what to do with moves that conflict with your existing repertoire, and tracking which of your moves came from which course. If we were going to do it, we should do it right, so I’m not regretting that decision. But if we anticipated all this difficulty ahead-of-time, we may have chosen not to take this one on.

Model games: I’m really happy with this one. It’s a great feature both from a product perspective and a user perspective. It’s one of the features I use most. I think it’s as effective of a learning tool as the opening training at the core of Chessbook. I’ve heard from an FM who won his national blitz championship, and most of his training consisted of using our model games. All credit here to my cofounder Ollie, who really pushed for this feature in general, but also drove for a high quality bar.

Repertoire statistics: This one is less on the learning efficacy side, and more on the engagement side. It’s really fun and gratifying to see that your repertoire performs well compared to your peers. It’s also great to see your mastery going up a bit with each review, because reviews can feel endless otherwise. Like the pre-made repertoires, this was a lot more complicated than anticipated, but I think the juice was worth the squeeze in the end.

Streaks: This took like 2 days to implement, and it should have been done ages ago. It’s just an easy win for retention. There’s a reason every other educational app does it.

Openings report: I think this is a great idea, that we may have just not quite figured out how to present. It’s useful and I check it fairly often, but it doesn’t feel like we really nailed it.

We’re not getting an A+, but overall I’m happy with the product direction that led to us choosing these features over 30 others.

Marketing

2/5

Ugh. I’ve tried paid ads virtually everywhere (Meta, X/Twitter, Reddit, Google), I’ve tried affiliate programs, word-of-mouth stuff like free merch to people going to tournaments and clubs, running chess tournaments, sponsoring streamers…

The one lever I’ve found that works is sponsoring YouTube channels. And I have had some lucky breaks there with larger creators. Unfortunately it’s not something I can just throw a ton of money at, and it’s quite draining to reach out to 20 channels and hear back from one. 90% of the time, it’s a bad use of my time.

I think the problem is that I feel antsy to do something with marketing, and putting money into paid ads feels like doing something, even if it reduces down to burning money. I’ve spent way too much on paid ads, with little to show for it.

The way to get to a passing grade of 3/5 here, may just be to give up. We have great organic growth and some persistent traffic from existing YouTube sponsorships that get recurring traffic. It still won’t get me to a 4/5 or 5/5, that would take some actual marketing skill. But I just don’t seem to have the touch. Or maybe this is an exceedingly hard tool to market, due to low LTV and a hyper-niche market. I think that’s a cop-out though, I do believe that someone could have made it work, under the same parameters.

Onboarding

3/5

Onboarding is hard. I wrote a bit about that here. For most of the year I knew we should be doing better in onboarding, but I didn’t prioritize this enough. Once we took a week or two to figure out what was going on and how to improve it, we improved our onboarding conversion rate by 2-3x. I think current onboarding deserves a 4 or 5, but leaving it so long was a big unforced error, so I’m only giving myself a 3 out of 5.

There wasn’t any silver bullet here. Just hundreds of golden BBs, as Alex Hormozi says. One bit I’m particularly proud of though, is that we figured out that onboarding rates are highly dependent on the user’s chess level, and that we needed to branch a bit to make onboarding nice for both beginners and intermediate/advanced players.

Stability

4/5

Stability is much improved from last year.

For overall service uptime, I think we’re at 3-4 nines, or 1-10 minutes of downtime per week on average. This is mostly due to a few bad commits and migrations over the course of the year.

For regressions and bugs, we’ve also reached a much higher bar this year. Part of that is just due to a maturing product, and part of it is holding myself to a higher standard now that we have thousands of paying users that depend on a reliable website/app. I don’t shoot from the hip as much anymore.

There’s still some ongoing bugs that can sometimes cause the server to restart. Thankfully restarts are quick, but I’d love to fix these so I can be more confident in the server.

Performance

5/5

I’ve made huge strides on the performance front this year.

One win was around compressing chess moves and positions. With some hand-rolled FEN compression, some arithmetic encoding of PGNs and SANs, and some cheating with common openings, we’ve reduced the size of each entry in our database by an average of 90%. This has meant retrieving records from Postgres is way faster, as we were I/O bound previously (shout-out Cloud SQL by GCP, for their anemic SSDs with 300MB/s reads).

Another win is adding a CDN layer in front of the frontend web server. I didn’t really feel this pain personally, being located near our servers, but my cofounder in Australia is certainly seeing the benefits here, along with the tens of thousands of users we have outside North America.

Besides the big wins, I’ve just generally put in the hours to figure out and tackle performance issues. Unfortunately after all this work I don’t really top-line metrics to compare, but I think overall Chessbook is 2-3x faster than it was at the beginning of the year. This difference is most felt by users with large repertoires.

There are some stability issues that are impacting performance, but I’m going to bucket those as stability issues and keep the 5/5 here.

Overall

4/5

I think I did pretty well! Onto the next year.


  1. Absolutes are never1 true ↩︎ ↩︎