Durable tech

I pay hardly any attention to anything that can be considered the “tech du jour”. When it comes to tech I’m a late adopter (if not a laggard), and unapologetically so. Sometimes I miss out on really cool tech that works amazingly well, but most of the time new tech is a dead end. Either it doesn’t catch on, or it does but it doesn’t work well, and even then it’s likely to get displaced by the new hotness in just a few years.

Take smart devices. Are smart home devices here to stay? I don’t doubt it. Do I want to have my house filled with the kind of smart devices that are for sale today? Absolutely not[1]. Smart devices are still hobbyist devices. For people who enjoy tinkering and the never-ending process of making different brands and protocols work together. Do I want a wifi-enabled garage door opener that breaks when some cloud service goes down? I don’t. Nor do I have the patience to upgrade the firmware on smart lightbulbs.

That these smart home devices don’t work properly isn’t the worst thing. The worst thing is that they demand constant time and attention and that the process of getting things to work is never educational. You can’t get “good” at home automation. You just google and mess with config settings until it starts working and then you hope it will continue to work for as long as you don’t touch it. (If only!)

I like to fix things such that they stay fixed. And I like to learn skills in the process that will serve me well for the decades to come. Because it’s this combination that allows you to move forward and go fast. The more fragile tech you use the more energy you waste on ducktape fixes. The more fashionable your tech is the more energy you waste memorizing minutiae. And then you have to migrate your stuff from the old way to the new, starting the process all over. Chasing new tech can feel like work, but you’re really just tiring yourself out without getting anywhere.

Once you’re on the lookout for durable tech you find nondurable tech everywhere. Web frameworks have notoriously little durability. When you memorize the concepts of a web framework you gain short term productivity, but you don’t acquire any fundamental knowledge. If you’re not careful you’ll spend your entire career chasing the new hot thing in web tech.

If you learned Javascript 10 years ago, haven’t used it since, and decide to make something today, it’s very easy to get back up to speed. Browsers have gotten amazingly fast, but this benefit you get for free. You now have “let” instead of “var”. You have real dictionaries instead of Object. In total there are maybe 100 new additions, and you can learn about all of them in an afternoon or two. JavaScript has gotten way better, but fundamentally it’s still the same language.

By contrast, if all you know is jQuery (15 years old) or AngularJS (10 years old) you now have a way steeper learning curve ahead of you. Frontend libraries like these don’t age well, and everything you memorized about the “angular” way to develop applications is not worth anything anymore. If you decide to learn another framework, React maybe, then you risk investing in what could turn out to be a technological dead end. Will reactive design be the way we develop apps in the next 20 years? Probably not. And apps built with “legacy” web stacks? They become unmaintainable, so you’re pretty much forced to rewrite them. Good luck finding people who want to work on an Angular 1.0 app in 2022.

Software written in C 20 years ago still works flawlessly today. Old DOS games can run in Dosbox or a browser emulator. Webapps that are a hairball of ducktaped libraries and microservices fall over unless you do constant maintenance. Some tech endures while other tech falls apart. The difference is night and day.

The Ancient Roman Bridge, a Timeless Engineering Feat | by Richard Bruschi  | History of Yesterday
This roman bridge is I don’t know how old, but it’s still here.

You want to invest time and effort in technologies that clearly won’t go away. Becoming good at vim or MS Excel might not be an optimal time investment, but the time isn’t wasted because even 20 years from now vim will be vim and and excel will be excel. Being able to ssh into any machine in the world and being able to effectively edit files is extremely useful today just like it was 10 years ago. When you’re good at Excel you can quickly crunch some numbers and plot some charts. Why spend days or weeks on dashboards that track metrics when you can do the same in Excel in a fraction of the time?

Durable tech skills are knowing fundamentals of C/C++, Python, JavaScript or any other mainstream programming language. You can learn vim, bash, strace, and rsync. These utilities aren’t going anywhere. You can learn algorithms and you can learn how to relational databases work, instead of some company’s proprietary API or ORM. Write things yourself, from scratch, with as few dependencies as possible. This way, once something works it will continue to work, maybe forever.

I can’t predict the future, but one trend is clear. Simple, monolithic systems that are rock solid tend to continue to work. Fragile systems composed of many libraries, services, and glue code start out unreliable and never seem to get better. Look for durable tech and try to make things that last.

[1] the only smart device I own is a Spotify connect receiver that stops working every 2 weeks and the only “fix” I found is to hard reboot by pulling the power cord. It also has HDMI CEC which works 70% of the time.

Early user feedback

When you launch the first version of your product it’s likely to have serious flaws. If you want to discover what these flaws are you’ve got to figure out (a) what your users use your software for and (b) what changes you need to make to make them happier.

This sounds trivial, but it’s not. Especially when you’re busy building you can easily forget to pause and think in this critical phase and shred your chances for future product/market fit in the process.

First, you want to know what your users are trying to accomplish. This tells you if your users are who you anticipated using your software. If you have the -right- kind of users you want to figure out if your product website is targeted at them, and if your marketing efforts are successful at reaching them.

What do I mean by “right users”? Well, very early adopters tend to be different from the users you’ll get down the road. Early adopters are more technical, care more about power-user features, are less inclined to pay for software, and more likely to give extensive feedback. This initial set of early adopters is really valuable because they are so willing to help. But in most cases your core customer base will be different and also have different priorities. This mismatch is dangerous. Early adopters can lead you astray.

It’s not just early adopters that are unrepresentative. Any users that use your product in ways you hadn’t anticipated are dangerous to listen to. It’s too easy to get persuaded your app needs to grow towards a very specific niche, when really, by doing so you alienate 90% of your real user base. Always listen carefully to all feedback, but if the feedback comes from the wrong type of user it’s best to stay the course. If you really need to make a big change in direction your users will continue to remind you, so no need to do anything rash.

The world is a big place. If your first 100 users are lukewarm about your app it could mean that your app stinks. But maybe your app is just confusing to use for the first time. Or maybe your website gives the wrong impression. Lack of enthusiasm is not a great sign, but neither does it mean you have to pivot or redesign or start over. Minor tweaks will do. Look for a new set of users from a different pool, and see if you get a better reception.

Remember that users who give feedback unprompted are outliers. You’ve got to reach out to the quiet users, too! Track, in the most primitive way, whether users are active. If you have users who use your app every day for a month that’s a very clear sign you’re on to something. Email them, and ask them what the #1 feature is they’d like to see. It’s so easy to do, and you might discover that this quiet set of users are your core market and deserving of your full attention.

Solicit feedback in the app itself. Add a feedback button. Maybe add a poll so users can vote on something. Lower the barrier to give feedback — any feedback — as much as you can. This way you’ll get feedback from a wider group of users. And be really responsive and thoughtful in your emails to users. Most people expect poor customer support, so they won’t go out of their way to write down what they think. If you get the chance to surprise people with good customer support they become way more likely to suggest improvements to your product.

However, be weary of people who email you pages of ideas but who haven’t really tried your software yet. It’s a lot of fun to talk to people and to get excited about the different directions your product could go in. But it’s also a distraction if this conversation is with people who haven’t really explored your product. When people have a pressing need they really try to use a product to see if it solves their problem. They’re willing to put up with flaws and shortcomings in your product as long as you get the vital stuff right. And if they haven’t tried your product that tells you something. Which user sends the feedback is as much of a signal as the content of the feedback.

As for the features/changes you want to make, we’ve written about that before. In short: make the first impression really good and nail down the core user experience. Many good features you won’t have time to build for another year or two and that’s normal.

Products want to be platforms

Imagine two startups. One startup makes a business chat app similar to Slack. The other startup has a business wiki product. In principle, these are very different apps that solve different problems. In practice, both apps face constant pressure from their customers to become more like the other.

Users of the chat app start demanding threaded discussions, archived discussions, pinned discussions, rich text options, and the ability to draft responses. Any individual feature might make customers happier and increase retention and conversion rates, but mindlessly adding all these features will turn a good product into an unfocussed mess. You end up with an ugly hybrid app that is half chat half wiki. It doesn’t have a clear audience anymore.

You can see how a wiki app can end up in the same place by adding more and more messaging features. Different chimera, but equally bad.

It’s a problem that all b2b SaaS apps struggle with. If you say yes to every feature request your product eventually turns into a platform product like Office 365. Customers always ask for Forms, workflows, calendars, project management, chat, writing, and email features. And they’re not wrong to want a single integrated solution as opposed to a hodgepodge of overlapping products. If they like a product they want it to solve more of the problems they have. So what is the app developer supposed to do?

As a SaaS vendor you have a couple of options. One option is to embrace the platform vision. Max out on engineers and build it all. This is the Google Suite, ZoHo and MS Office 365 strategy.

A second option is to stay strictly on course. You build out your narrow product and make it the best it can be without adding any platform features. Instead of making your product wider you go deeper. You keep your focus and you refine and perfect your product, and users will have to use an API for everything else. You get really good at saying “no” to customers. The big disadvantage of this approach is that you spend a lot of time making small improvements only a handful of powerusers care about while ignoring the features your customers really want but that don’t fit neatly in your product vision.

A third option is the “tiny platform” approach. Instead of building a whole platform you only build the easiest 5%. You implement a couple of key features of each platform product and go no further. This is the Basecamp approach. The big disadvantage here is that you end up with a product that is okay at many things but doesn’t excel at anything.

A fourth option is to make your product an add-on to an established platform like Office or Google Suite. You focus on your product features and you can provide integrations with the other application suite products for everything else. This strategy makes a lot of sense, except, you tie your future to a 3rd party that really doesn’t care about you. It’s a precarious position to be in. The platform owner can pull the plug at any time. One day you’ll wake up to an automated email that an API function has been deprecated. Your app stops working. Your customers are angry, you are helpless, and years of work go down the drain.

There is no correct approach to this platform dilemma. Software is never finished and you can always keep building and building in any direction. Build in the right direction and you’ll get many happy users. Build in the wrong direction and you’ll attract the wrong kind of customers that will push your product even further away from product-market fit. That’s why it helps to have a clear platform strategy. It can tell you when to say no.

This is what we have to figure out for Thymer. Thymer will have task management and planning features, but this has some overlap with the features provided by conventional calendar software. And everybody already has a calendar app that Thymer can’t realistically replace. Neither can Thymer replicate all major functionality people have come to expect from calendar software. So what do we do? Do we go all-in on API integrations with 3rd party calendar software? Do we tolerate scope creep and reimplement all calendar features we think are important? Do we add a minimalistic calendar to Thymer and accept that users will still have to use a different calendar product on the side? Or should we kick the can down the road in the hope the correct answer will become clear in the future? I don’t know.

Enjoying the journey

I’m back from my holiday! We went on a simple road trip from Eindhoven to Marseille, at the Mediterranean sea, with plenty of stops along the way. We enjoyed some good food, hiked in nature, did some sightseeing, read some books. Now I’m well rested and excited to get back to work.

Whenever I sit down at my desk after being away for a while I get the opportunity to look at my work with fresh eyes. We tend to work on our projects for a long time. Years on end, usually. It’s easy to lose focus of the big picture when you’re always heads-down working. Stepping back for a while can give you valuable new perspectives.

Sometimes, when I return from a break I feel energized and ready to continue working right where I left off. This is the best case scenario. Other times, I ask myself why I’m working on this specific thing, when I could be working on any number of other things instead. In a conventional job it’s the responsibility of other people to figure out what what’s important and what you should be working on. When you make your own products you have to figure it all out yourself. Breaks are a great time to think about these big picture questions.

Taking a break costs time. There is no denying that. If you don’t plan ahead for breaks your schedule will slip. That’s a little bad, but not a disaster by itself. What really matters is choosing the right things to work on and actually launching. Launching a week or a month or a quarter late doesn’t matter in the big picture. It’s still better to move fast, of course. But direction matters most. We’ve picked a direction with Thymer and we’ll launch with only essential features. This reduces the risk of we waste a lot of time time building the wrong thing and it’s easier to make big changes to small products.

Although our goal with 80daystartup is to get a product from 0 to market in a short time, we’re still in it for the long haul. Making a simple product that is good enough to charge money for is really just the first step in a much longer journey. Ultimately, we need to make a pretty great product to get the kind of traction necessary to make Thymer work as a business. That’s going to require rewriting big pieces of our app and ripping out those parts that don’t work. Ideally, we get everything right in the first beta version. But if the future turns out anything like the past then we’ll make some big product mistakes. It will take some iterations to fix those and to get to really great product market fit.

And even then, that’s still only the beginning. Turning a good concept into a rock-solid commercial product is a ton of work. If Thymer really takes off we’re likely to work on it for years before it’s really “done”. And that’s fine! We enjoy writing software, and we enjoy seeing people use the software we’ve written. We don’t know if Thymer will work out. We’ll discover in due time, but no matter what we’ll enjoy the journey.

Perils of caching

We try to design our software to eliminate entire classes of bugs. State updates happen in transactions so they’re all or nothing. We favor immutable state and INSERTs over UPDATEs. We like simple, functional (or idempotent) code that is easy to debug and refactor.

Caching, by contrast, exposes you to new classes of bugs. That’s why we’re cache-weary: we’ll cache if we have to, but frankly, we’d rather not.

Downsides of caching:

  1. Introduces bugs
  2. Stale data
  3. Performance degradation goes unnoticed
  4. Increased cognitive load
  5. Other tail risks

Caching introduces an extra layer of complexity and that means potential for bugs. If you design your cache layers carefully (more about that later) this risk is reduced, but it’s still substantial. If you use memcached you can have bugs where the service is misconfigured and listens to a pubic IP address. Or maybe it binds on both IPv4 and IPv6 IPs, but only your IPv4 traffic is protected by a firewall. Maybe the max key size of 250 bytes causes errors, or worse, fails silently and catastrophically when one user gets another user’s data from the cache when key truncation results in a collision. Race conditions are another concern. Your database, provided you use transactions everywhere, normally protects you against them. If you try to write stale data back into the database your COMMIT will fail and roll back. If you mess up and write data from your cache to your database you have no such protection. You’ll write stale data and suffer the consequences. The bug won’t show up in low load situations and you’re unlikely to notice anything is wrong until it’s too late.

There are also some more benign problems caused by cache layers. The most common one is serving stale data to users. Think of a comment section on a blog where new comments don’t show up for 30 seconds, or maybe all 8 comments show up but the text above still says “Read 7 comments”. In another app you delete a file but when you go back you still see the file in your “Recents” and “Favorites”. I guess it’s easier to make software faster when you don’t care about correctness, but the user experience suffers.

Many other types of data duplication are also caching by another name. If you serve content from a CDN you have to deal with the same issues I touched on above. Is the data in sync? What if the CDN goes down? If content changes can you invalidate the CDN hosted replica instantly?

Maybe you decide to reduce load from your primary database server by directing simple select queries to read-only mirrors. It’s also a form of caching, and unsurprisingly you have to deal with the same set of problems. A database mirror will always lag behind the primary server. That can result in stale data. What if you try to authenticate against the mirror right after changing your password?

Caching layers can also obscure performance issues. An innocent patch can make a cheap function really slow to run, but when you cache the output you won’t notice. Until you restart your service and all traffic goes to a cold cache. Unable to deal with the uncached load the system melts down.

Things that used to be simple are complicated now. That’s really what it all boils down to. Caching layers complicate the architecture of your system. They introduce new vectors for serious security vulnerabilities, data loss, and more. If you don’t design your caching abstractions carefully you have to think about the caching implications every time you make simple changes or add simple features. That’s a big price to pay when the only benefit of caching is performance.

Alternatives to caching

  1. Buy faster hardware
  2. Write smarter algorithms
  3. Simplification
  4. Approximation

Buying faster hardware is an underrated alternative. Really fast servers are not expensive anymore, and dedicated servers are much faster than anything you can get virtualized on the cloud. Dedicated servers have their own downsides, I won’t deny that. Still, performance is just much less of an issue when you have a monolithic architecture that runs on a server with 32 cores and 500gb of ram and more NVME storage than you will ever need.

Algorithmic simplifications are often low-hanging fruit. Things get slow when data is processed and copied multiple times. What does a typical request look like? Data is queried from a database, json is decoded, ORM objects are constructed, some processing happens, more data is queried, more ORM objects are constructed, new data is constructed, json is encoded and sent back over a socket. It’s easy to lose sight of how many CPU cycles get burned for incidental reasons, that have nothing to do with the actual processing you care about. Simple and straightforward code will get you far. Stack enough abstractions on top of each other and even the fastest server will crawl.

Approximation is another undervalued tool. If it’s expensive, for one reason or another, to tell the user exactly how many search results there are you can just say “hundreds of results”. If a database query is slow look for ways to split the query up into a few simple and fast queries. Or overselect and clean up the data in your server side language afterwards. If a query is slow and indices are not to blame you’re either churning through a ton of data or you make the database do something it’s bad at.

Our approach

The kind of caching we think is mostly harmless is caching that works so flawlessly you never even have to think about it. When you have an app that’s read heavy maybe you can just bust the entire cache any time a database row is inserted or updated. You can hook that into your database layer so you can’t forget it. Don’t allow cache reads and database writes to mix. If busting the entire cache on a write turns out to be too aggressive you can fine-tune in those few places where it really matters. Think of busting the entire cache on write as a whitelisting approach. You will evict good data unnecessarily, but in exchange you eliminate a class of bugs. In addition we think short duration caches are best. We still get most of the benefit and this way we won’t have to worry that we become overly reliant on our cache infrastructure.

We also make some exceptions to our cache-weary attitude. If you offer an API you pretty much have to cache aggressively. Simply because your API users are going to repeatedly request the same data again and again. Even when you provide bulk API calls and options to select related data in a single request you’ll still have to deal with API clients that take a “Getta byte, getta byte, getta byte” approach and who will fetch 1 row at a time.

As your traffic grows you’ll eventually have to relent and add some cache layers to your stack. It’s inevitable but the point where caching becomes necessary is further down the road than you’d think. Until the time comes, postpone caching. Kick that can down the road.

Making sense of contradictions

There is a ton of startup advice out there, and much of it is contradictory. Let me explain what I mean with some examples:

  • Entering a crowded market: It’s smart because in a crowded market you can always find people looking for a new product. Or: It’s risky because it’s difficult to make your product stand out from the crowd.
  • Product pricing: You should charge as much as possible, that way only you need to find a handful of customers to become “ramen profitable”. Or: make your product inexpensive so you can undercut competitors and enjoy free word of mouth marketing.
  • Rapid prototyping: Smart because it allows you to quickly validate if your business model works. Or: It’s unwise because if you rush to market your product won’t be great.
  • Originality in product: It’s good because people don’t get excited about things they’ve seen before. Or, maybe, it’s bad because it means you’re solving a problem that doesn’t exist or nobody cares about.

It’s not hard to come up with arguments for or against anything when it comes to software startups. This is in part because the world of software is big. Strategies that make sense for a lavishly funded startup in the valley won’t work for a bootstrapped company in Japan. A biotech startup has different constraints than a simple web startup.

Contradictory startup advice is also common because there are many different ways to get a company off the ground. What worked for others in their particular situation probably won’t work for you. Different time, different product, different market, different people. Every startup is unique and the world is changing rapidly.

When you get or read advice ask yourself if the opposite could be equally true. It easily could be. It’s up to you to figure out if you should follow well-meant advice or do the opposite. It’s rarely obvious. So take the time to really think everything through. As a rule of thumb, you can learn the most from founders that are slightly ahead of you and that have a product in a similar but different market. Then you can look at what they do and see if it works. What does their marketing and sales strategy look like? Is their product good? Do people talk about their product on social media? What do they say? How and where they they get their first users? If you keep your eyes peeled for evidence like this you’ll learn a bunch.

DIY javascript error logging

There are many SaaS products out there that help you with javascript error and event logging, but in this blog post I want to make the case for rolling your own solution.

We log 3 types of events: (1) javascript exceptions with stack traces, (2) failed assertions, and (3) general usage/diagnostics information.

Exception handling

We can use a global event handler to log exceptions. This used to be somewhat difficult, but nowadays window.onerror works great. The browser gives you everything you need. Straight from the mozilla docs:

You can even get a pretty good stacktrace with Error.stack. It not part of the official web standard, but it works on all major browsers and that’s good enough. Once you’ve collected all data you want to log you can just send it to your server with an ajax request. Alternatively, you can use an <img> tag. Something like this works just fine:

let errimg = document.createElement('img');
errimg.src = '/jserror.png?e=' + encodeURIComponent(JSON.stringify(obj));

One thing to watch out for is that GET requests can get truncated. You also want to make sure that you don’t log errors when you’re already in your error handler (otherwise you’ll DDOS yourself :)) and you probably want to drop errors you’ve already reported. Reporting an exception once per session is enough for debugging purposes.

What metadata you want to log is up to you, but we find it useful to log these things:

  • Username, account name. If you find a bug and fix it you want to tell people you fixed the bug, but you can’t do that if you don’t know which people got the error message.
  • Browser version. Helps when you want to replicate the bug. This was super important back in the IE6-9 days, when you had to make tons of browser-specific workarounds. Nowadays you mainly want to know if people are using a really old or unusual browser.
  • Javascript app bundle version and page load timestamp. Some people keep their browser open for weeks at a time and you don’t want to waste hours trying to replicate a bug that has been fixed ages ago.
  • Adblocker usage. Add a <div> with a bunch of spammy keywords to your page. Use setTimeout to check the boundingRect of that node a couple seconds after your page has finished loading. If the node is gone, you know they have an adblocker installed.

Be careful not to log anything that could contain customer data. Easier debugging is great, but not when you have to compromise your customer’s privacy to do it. It’s fine to log counts, IDs, and checksums. If you can’t figure out how to replicate the bug with only a stack trace to guide you then you can always add more asserts to your code and wait for one of them to trigger.


To debug exceptions you only have a stack trace to work with. Debugging is a lot simpler when you make liberal use of assertions in your clientside code. You can use the same error logging code you use for exceptions, but asserts can log some extra diagnostics variables.

Usage tracking

Every time you add a new feature to your product you want to track if it gets used. If not, figure out why not. Is the feature too hard to discover? Do people just not care about it? Adding a tracking hook takes 1 minute, but the insights you get are invaluable.

Our rule of thumb: we get an email notification every single time a new feature is used. If the notifications don’t drive us nuts that means we built the wrong thing. This really helps us calibrate our intuition. And it’s motivating to see the notifications flow in right after you push to production!

You also want to track how often users get blocked by your software. Every time a user wants to do something but they get a “computer says no!” message they get a little bit unhappy with your software. They upload a file and it doesn’t work because the extension is wrong or the file is too large? Log it and fix the problem. Sometimes the fix can be as simple as telling users the file is too large before they have uploaded it. Instead of a simple “access denied” error see if you can make the error more helpful. You can add a button “ask administrator (name) for permission”. Measure which problems users run into fix them one by one.


We take a whitelisting approach. We get email notifications about everything to start with. Then we add filters for all the errors we can’t do much about. Errors caused by connection timeouts, errors caused by virus scanner browser plugins, things like that. Every javascript exception potentially breaks your entire site for some users. That means every exception is worth investigating. You’ll inevitably discover your site breaks when an ajax POST times out, or when a dependency fails to load. Or when an adblocker removes some DOM nodes. No matter how well you test your software, your users will find creative ways to break it.

You can also use feature usage tracking for spam/fraud detection. If your SaaS service is inexpensive it will be used by credit card fraudsters to test if their stolen credit cards work. You can easily distinguish between real users and bots or fraud signups by comparing some basics statistics on feature usage and which buttons have been clicked on.

If you use a 3rd party service for error logging you can’t cross-reference data. You can’t easily discover which features get used by people who end up buying vs by trial users that fizzle out. If you have subscription data in one database and usage/error tracking in another database querying gets complicated, so you won’t do it.

Another reason why we want to do our own event logging is that we might accidentally log something that contains sensitive data. Our own logs rotate automatically, but 3rd party logging/event services will hang on to that data indefinitely.

Writing your own javascript error/event logging code isn’t much work and it will give you valuable insight in how people try to use your software and the bugs they run in to.

Magical thinking

Most startups fail. Some startups don’t launch at all. Maybe the founders get into a big fight, or they lose interest, or the product is too hard to build, or something else throws a spanner in the works. Other startups launch but don’t get any traction. Either they get no signups or the users don’t like the product. The founders then get discouraged and quit. Then there are startups that pass the first two hurdles but stumble at the third hurdle: they launch and get a bunch of happy users but they fail to monetize. Failure at different stages but failure all the same.

With our app we really want to pass all three hurdles: launching, marketing, and sales. There is some luck involved and we can’t discount the possibility we’ll fail as well. Things beyond our control can take us out. That’s part of the game and we accept that. What we don’t want is to fail because of unforced errors. We don’t want to do dumb stuff that predictably results in failure. We want to learn from other startups what works and what doesn’t.

I’m going to describe three different startup failure scenarios that I see time and time again. I’ve anonymized the stories because my goal here isn’t to make fun of people who failed but to analyze what happened so we can learn from it.

Our fictionalized founder makes three apps over the years:

  1. a desktop app that helps you organize your music collection on Linux
  2. an iPhone app that tracks and charts your grocery expenses
  3. a job listing webapp

The projects fail for different reasons but with a shared root cause. I’ll get to that later. First let’s go over these startup attempts.

Attempt 1: Linux music library app

Two common pieces of startup advice are to (1) build with the skills you have and (2) solve a problem you understand well. Our protagonist takes this advice very literally and decides to make a desktop Linux app. He has written software for Linux before and he knows he can make a better app for organizing your music.

Our heroic founder spends nights and weekends to get his music library app done. He starts coding in c++ for performance reasons, but eight months in he decides that programming that way just takes too long. The product architecture got a bit messy, so he decides it’s time for a rewrite in Rust. He doesn’t know Rust, but it’s a cool new language with smart memory management that he wanted to learn anyway. Fast forward a year later and the app kind of works. At this point the founder no longer has much passion for the idea. He’s not convinced people will buy his app. Working for two years straight in isolation is a long time and he decides he it’s time to launch. He spends two days on a website, but our protagonist has no web design skills and it looks pretty bland. He submits his site to Hacker News, reddit, and sends the link to some friends.

Crickets. His website gets a few dozen visitors. A download or two. Traffic drops to zero after a few days. Frustrated, the programmer abandons his project. He tells himself that failure is inevitable on the road to success. That failure isn’t real failure if you learn from it. He reflects on why his startup didn’t take off and concludes that the fundamental problem is that his app didn’t have a big enough audience because Linux is too niche. He figures he should have made something for “regular people”, instead. He also decides he should make something simpler.

With full determination he decides to try again.

Attempt 2: iPhone app for grocery expenses

Our founder has no particular passion for grocery phone apps but he figures that everybody shops for groceries so it should be a good and large market. If he charges $10 then for every 0.01% out of a billion iPhone users that buy his app he’ll make a million dollars (less 30% Apple fees and taxes). Previously the founder struggled with distribution and now he has Apple to take care of this for him. He also doesn’t need to worry about invoicing, credit card payments, or any of the other annoying business-y stuff anymore.

Our protagonist starts building. A year and two rewrites later he’s happy with the product and submits his app for review. His app is approved and soon after he gets the first sale! Making your first dollar of internet money very exciting. Then again, a trickle of downloads and one $10 sale per week won’t cut it if you need to make a living. But what to do? Lower the price? Ads? Rename the app? Change the logo? Translate the app to multiple languages?

Six months and many experiments later our protagonist is ready to give up. He can get more downloads if he charges 99¢ but he’s still only making beer money. Switching to in-app purchases results in more downloads, but getting people to upgrade is no mean feat. A few mean app store reviews later he’s completely done.

Okay, lesson learned, he thinks to himself. Next time, he’s going to make something SIMPLE with BUSINESSES as customers.

Attempt 3: job board

With renewed spirit our founder gets to work. Where previously he spent the better part of a year to get a prototype going now it’s just a matter of weeks. Job boards are pretty easy. What do you need? A place for people to look for jobs in their region. Search and filter by keywords and requirements. Done. What else? A backend for employers to submit new job openings. He decides a job listing costs $100 for 30 days. Slap a credit card form on the page and done.

This time our founder has a plan to get users. He’s going to contact hundreds of companies and recruitment agencies over email and LinkedIn with his pitch. He figures that by charging less than the established players he can win their business.

No dice. Companies just don’t want to advertise on an empty website. And job-seekers have no reason to visit an empty job board either. Catch-22. Our protagonist decides to reach out to a more experienced founder and asks for advice. The secret, he’s told, is to cheat. Create fake listings, fake users. Fake it all. Once you’ve got real companies and real job-seekers you can phase out the fake traffic.

The founder can’t believe what he just heard. That’s an ethical line he doesn’t want to cross. Disgusted, he concludes that startup life just isn’t for him.


Three attempts over many years amounted to nothing. Every time the founder believed he learned from his mistakes and would surely succeed next time, but he was wrong. The founder didn’t get anywhere because he learned only specific lessons: don’t make a Linux app; reaching the top of the App Store charts is hard; you can’t bootstrap a marketplace from nothing without cheating. The founder didn’t discover the root cause of his startup troubles and consequently didn’t learn anything of value. He could try five more times and he would continue to fail if he did.

The fundamental mistake this founder made is that he tried to arrive a good startup concept by reason. You can come up with reasonable sounding arguments in support for the most hopeless ideas. “Good arguments” therefore carry no weight at all.

Instead, the founder should have looked for evidence. Have other people found success with commercial desktop Linux apps for consumers? If yes, how did they succeed? Did they bootstrap, self-fund, or take VC funding? How did they find an audience? Can you find the wreckage of other people who tried what you’re about to do but failed? Why did they fail? If you can’t find success stories that’s big red flag.

It’s easy to explain failure with hindsight. There is no commercial competition for Linux desktop apps because Linux users don’t pay for software. The Apple App Store by contrast is highly competitive. You have to figure out why apps succeed and fail in app stores before you spend a year on an app of your own. You need a captive audience of businesses and professionals in search of jobs before you start on your job board or you’ll fail. But these specific explanations are not important. What matters is the process of looking for evidence that supports or invalidates your startup idea before you commit. The alternative is magical thinking and it looks like this:

The diagram doesn’t look like this in the founder’s head. The founder has all sorts of strategies and plans that sound great, but plans that aren’t backed up by evidence are just wishful thinking. If you remove everything based on wishful thinking what you’re left with looks like the flow chart above. Build. Launch. Pray. Give up. (Repeat.)

The founder also made many other mistakes. But so what? Everybody makes mistakes. Mistakes can be fixed if your startup gets the important things right. Your best best is making an original product for an established market and by applying sales/marketing strategies that are known to work.

Written like this it seems trivial. But it’s not. I see an endless amount of magical thinking by founders who earnestly believe that just making something and hoping for the best will work out for them. I’m not judging, though! We’ve also spent years on hopelessly flawed products. When we failed we totally deserved it.

I mentioned in the introduction that we intend to pass the three startup hurdles of launching, marketing, and sales. For each of these hurdles we’ve collected clear indicators that our general approach should work. We’re intentionally a “single miracle startup“. We’re taking an educated gamble with our product, but our marketing and sales will be conventional and straightforward. Our plan may fail, but we have a real plan.

High conviction, low conviction

We’re making a todo/planning IDE. Think Visual Studio Code for tasks and projects. For our startup to work we have to get three pillars more or less right: product, business model, and marketing. Marketing because you don’t get anywhere when nobody knows your product exists. The business model is the part where we figure out how to make money. Most importantly we have to make a product people love.


We want to make Thymer text based. The design trend over the last decade has been towards shiny interfaces with lots of whitespace and poor usability. Software has gotten easier to use but also dumbed down a great deal. We want to go in a different direction. We want higher information density, a text based interface, extensibility, and we don’t want simplicity at the expense of powerusers. On these things we are high conviction.

We have a clear vision of what we want to build. If we deviate from this vision based on early feedback we’ll end up with a product that lacks cohesion and that nobody will get excited about. We can pivot somewhat, but we can’t change what our app is fundamentally about. If it turns out that people just don’t want this kind of software (or don’t want to pay for it) we’ll throw in the towel and build something else.


These are some of the marketing channels we can use to get the word out:

  • Social media (reddit, hacker news, linkedin, discord, other communities)
  • Google ads, Twitter Ads
  • Youtube channel
  • SEO marketing pages to boost organic search engine traffic
  • Twitter
  • Magazines
  • Blogging
  • Guest spots at podcasts
  • Guest blog posts
  • Bulk deals (e.g. Appsumo)
  • Integration with 3rd party products or protocols
  • Viral features/word of mouth

We won’t know which marketing channels will work for us. Presumably one or two marketing channels will end up producing the bulk of the traffic. We’ve just got to try everything and we’ll discover what works in the process.

When it comes to marketing and distribution we’re low conviction. We’ll have to figure it out as we go. As long as we get traffic and trial signups I’m happy.

Business model

On principle we’re only willing to try a couple of business models:

  • Freemium (mostly free users; small percentage upgrades to paid)
  • Freemium + B2B SaaS (also have subscriptions for businesses)
  • One time fee for lifetime access
  • Pay for usage or flat fee?

We don’t want ads in our product, but any combination of free+paid we can try. We can make some educated guesses on what works, but ultimately consumers aren’t rational agents. How much users are willing to pay, if anything at all, depends on emotional factors that are hard to forecast. Can we charge based on the value we provide? Will customers price anchor based on competitors? Will customers feel strongly that software should never cost more than $X? We don’t know. Needless to say, with regard to pricing, we’re low conviction.

If we can’t drive enough traffic to our website to get trial signups in volume, then maybe we’ll pivot and introduce features that appeal to businesses. As we wrote about earlier, a business subscription is easily worth 30x a single user subscription. A B2B SaaS product can thrive on a trickle of traffic.

Marketing is the real bottleneck and biggest unknown. Given enough traffic and enough trial users I’m confident we’ll be able to fix all major issues in our product and the business side will take care of itself.

Wild ideas for Thymer

When you’re working on your first prototype for a new product you’ll inevitably come up with many wild ideas. It’s just part of the creative process. If you are a disciplined you won’t let yourself get distracted by actually building any of them, but it’s fun to think about what your product could be like one day.

Right now we focus 100% on what is strictly necessary to get a prototype done. The prototype has to have just enough functionality for us to get useful user feedback, but no more. Then we can iterate based on that feedback and fix flaws in our design.

That means we can’t add any of the stuff below to Thymer. Not yet, anyway.

Blog plugin. Use Thymer editor to write simple text based posts + maybe pictures. Render to static .html to publish.thymer.com or something like that. Maybe let users write public and private blog posts. If the Thymer editor is really good and you already have your notes in it, being able to share them makes sense. You could also share posts privately, with end-to-end encryption. Users would then need to enter a password to decrypt the shared note. It’s an easy plugin to write, and maybe it’s something people would like.

Kanban board plugin. Instead of rendering Thymer tasks as a vertical task list we can render them horizontally with tiles like a Kanban board. Add support for drag&drop. Enjoy easy planning.

Thymer is effectively a tree editor. Allowing users to view/edit their data in different and visual ways is probably an easy win for us.

Transclusions. A transclusion is when one document appears partially inside another document. Digital documents don’t need to be strictly linear, and it would be very cool to have something that’s more powerful than a plain hyperlink to another part of your Thymer document. We already need to make something where a planning view (an ordered list of things you plan to do next) can have items from anywhere on your document on it. Generalizing this concept and adding full-blown transclusions to Thymer might be worth it, because it unlocks opportunities down the road.

End-to-end encryption. Thymer is an offline-first app. You download all your data when the app loads, and it syncs in the background when/if you’re online. Because the app has to work offline the server doesn’t need to get involved during normal operation. That means we could just encrypt all data locally. Then the server would only see the tree structure of your document, but nothing content-wise. The major downsides here are that we won’t be able to offer server-side search, a practical API, or 3rd party integrations if the server can’t read the data. Personally I strongly favor apps that put encryption front and center. We’ll have to wait and see what the beta users think.

Roadmap plugin. Thymer will get a personal schedule where people can add tasks they plan to do Today, Tomorrow, Next week or whenever. But sometimes you want to do higher level and long term planning. We could make a plugin for that. It’s unclear at this point how it would work exactly, but if you have 5 or 10 people in a team you want to have some kind of overview where you can see what’s happening in broad strokes. Maybe this overlaps with Change History and Meeting Mode below. I don’t know.

Poweruser console. When you’re working with your own software you do bulk actions all the time. You find some objects based on some criteria, and apply some changes with a for loop. A REPL or a browser’s debug console are huge for productivity, but consumer apps never offer something like that. Why not? You can tediously make changes with point and click. If you’re lucky the app allows you to bulk move/delete, but that’s where it ends. I’d really like Thymer to have a REPL. Then users can do whatever they like with their data. Users of productivity apps have their idiosyncrasies, and the more Thymer can accommodate how people like to work the better.

Change history. Some way to see a what changed in a condensed way. If you get back from a holiday you want to see what people have been working on. How has the schedule changed? What items have been deleted/abandoned? Who is currently working on what? What decisions have been made in your absence? Ideally, you’d get some kind of executive summary that isn’t too long. Because the Thymer document is a tree it’s reasonable to to assume things higher up in the tree are more abstract/high level. Maybe a summary view can condense changes based on that.

Emoji Flags. People want to use their task list in different ways. We won’t be able to anticipate all our user’s needs, but we can provide people with options to organize their tasks to their own liking. We can store arbitrary metadata in tasks. Allowing users to choose their own status emojis doesn’t seem like a big stretch. Want to give a task a ⌛ or 🐳 status? Sure, why not.

Meeting mode. During a meeting some tasks get deleted, new tasks get added, tasks get assigned and reassigned, and people’s schedules get changed. You want to see what has been changed during the meeting separately from other changes made to the Thymer document throughout the day. Ideally meeting notes that contain the rationale get linked to the changes made to the Thymer document. I don’t think it makes sense to have meeting notes that duplicate the changes made in the Thymer planning. But when there are no meeting notes at all the “why” goes missing. Somebody who hasn’t attended the meeting should be able to see what has been discussed and what has changed as a consequence. I don’t think there is any product out there that gets this right, so it’s probably more difficult than it looks.

Any project, even something as simple as a todo list, will grow in scope if you let it. You can’t just add every random feature to your product. Your product will lose its cohesion and you’ll never launch. But you can write down all your half-baked ideas. Once you’ve got some users go back to the list and see if you can actually make some of your wild ideas reality.