Most software is feature-ful: built from an amalgamation of individual features, which are usually atomic ways to do something specific, arranged in a singular interface that has to work for every user of that piece of software. Each feature is usually built with a small number of end-to-end user experiences in mind (often in the form of “user stories”). Each feature has to live somewhere in the UI; sometimes they’re put in unexpected or awkward places, and every so often the whole UI gets shuffled up in a redesign.
The problem with software features is that they’re inherently not user-friendly: they’re a creator-centric way of thinking about a software product (“what do I have to build?”), not a user-centric way of thinking about a software product (“what can I do?”/“how can I do this?”). From the perspective of a software user, it’s hard to determine whether a feature exists or not and how to get to a feature if it does exist. If a feature is accessible, users may still be confused or frustrated by specific details of the provided interface. And even if a feature works great otherwise, it can still be stuck inside a piece of software, requiring users to import and export data to use that feature.
Whether a feature exists or not
“CRUD” is an acronym used to describe software that allows you to Create, Read, Update, and Delete data. This broadly encompasses a lot of software that shows lists of data and allows users to click into list items to view or edit their details. CRUD software define a set of data models: e.g. Twitter can be roughly thought of as data models for users, tweets, photos, and likes. The “features” of CRUD software are the product (in the Cartesian sense) of the CRUD actions and the data models: on Twitter, users can create, read, update, or delete users (or at least your own user); users can create, read, or delete tweets, but notably not update tweets1. Using this framework, there’s a clear way to determine the set of possible features and whether any of them don’t exist.
In reality, most feature-ful software don’t have a clear taxonomy of possible features or a well-defined standard library of expected functionality2, which makes it difficult for users to determine whether a feature actually exists or not. Similarly, without a comprehensive way to organize functionality, software creators also don’t have visibility into feature gaps. Software creators might be surprised to discover the presence or lack of some functionality or struggle to answer users’ questions about whether they can do something. Without a clear taxonomy for features, it’s also harder to anticipate interoperability concerns between different features, sometimes leading to difficult-to-diagnose bugs when users use combinations of features that weren’t explicitly considered.
In practice, a lot of software gets built (or not) depending on whether an engineer working on the software product thinks of a feature, cares enough to see it through (which may involve quite a lot of time and energy depending on the company), and gets it prioritized highly-enough. In the meantime, users who want a particular new feature can’t do anything except wait3.
How to get to a feature
Every feature lives in a “physical” location in feature-ful software. If a user wants to know how to use a feature to do a thing, the answer is a procedure: something like “click this, then click that, then scroll down and click there”. It’s not necessarily obvious where to start — an app might have many menus, different sets of menus4, and unobvious interactions5 — and it’s a long way removed from being able to “just do” something. Sometimes, features even get further away over time: if the natural place for a feature is in a menu or sidebar, it’s likely that the menu or sidebar will get longer over time and the existing features in that menu or sidebar will literally move further away.
For software creators, having a feature-ful interface also means that building every feature comes with the cost of building a UI for it and manually making sure the feature is surfaced in every place where it might be relevant. Occasionally, feature-ful interfaces might get redesigned, resulting in a lot of work for software creators (often with no net-new user-facing functionality) and forcing users to learn new ways of doing the same things they used to do.
The lack of a functionality taxonomy also hurts feature discoverability — there’s no consistent way to determine whether a feature exists by searching for the desired output or the set of things that can be done with a particular input. There’s also no way to convey guiding principles to determine whether an end result should be achieved by composing basic functionality or by using a specific feature for that purpose6.
How specifically they function
Feature-ful software almost always ships with a singular interface for every feature — there’s only one way to invoke some functionality, designed by a small group of people, and meant to work for all of their users. Product designers have to make many assumptions and choose many defaults, working around many constraints that may not be visible or relevant to each individual user. If that interface is awkward or slow (even if it’s that way for good reason), users can’t do anything about it, except perhaps by finding a workaround7.
One interesting counterexample is Alfred: it’s essentially an alternative interface for Spotlight on macOS — Alfred uses Spotlight’s database but returns results faster and has more (and more configurable) functionality. Alfred is an example of how separating the data from the interface can lead to better interfaces for users who want alternatives … more on this below.
Well-designed, useful features may remain “stuck” inside a particular software product — users can’t benefit from those features in other software unless users import their data into that software product, use the feature, and output the result8 (or until other software products copy those features9).
In some cases, open-source libraries get created to implement notable new features, which helps these features propagate across software products. However, many valuable features are not so easily replicated in open-source libraries. Even if they were, the inability to use alternative interfaces means that gaining access to new features might require a user of one product to switch all their data to another product.
What might “feature-less” software — software that eschews the characteristics of feature-ful software — look like? I think there will be different software “roles”; individual software products can fill one or more of these roles.
As a prerequisite, the world of feature-less software will have a separation between data and UIs. Some software will mostly be responsible for reading and writing data, other software will mostly be responsible for providing UIs, and this may happen across different software products. This seems like a dramatic difference compared to modern SaaS software products or professional software products with opaque file formats or content libraries, but there is precedent: the filesystem is an OS-provided data layer that can be shared by different software products; the wide variety of text editors is an example of different UIs sharing a data layer comprised of plain-text files. The wide variety of email clients is an example of different UIs sharing an abstract data layer defined by the IMAP protocol. OSs also provide built-in libraries for accessing some types of data, such as Contacts, Calendar events, and Email messages.
Some feature-less software would be packages of novel capabilities: defining new data models, extending the schema of existing data models, or implementing new or alternative actions for data models. Such software may include zero, one, or more built-in UIs. In the zero-UI case, the software may exist to provide functionality that’s meant to plug into other UI hosts (more on this below). In the one-UI case, the software looks like the type of single-interface software that is broadly popular today, except that it can also include capabilities that are exposed elsewhere. In the multiple-UIs case, these could be entirely-different UIs (not just themes) meant for different user archetypes or workflows10, with a platform-native way of switching between them or viewing any number of them side-by-side.
Other software would primarily exist to provide or host interfaces, either as client UIs for individual products (for example, Tweetbot is a client UI for Twitter), or for users to assemble a particular workflow from capabilities provided by other software. The Photos app on macOS is an example of the latter — it supports editing extensions, which allow third-party apps like Pixelmator to run their own UI within the Photos app and share access to the Photos library. However, this example could be taken further: in theory, the Photos app could enable its users to build a custom editing interface by combining editing tools from different applications, or even by defining entirely-new tools that can be built from some basic UI components such as sliders and text fields and invoke a unit of custom code to produce the desired outcome.
This ability to build custom UIs will enable a long tail of “Lego-ized”, point-solution, feature-ful software specifically designed to solve individual line-of-business workflows. They’ll exist at the granularity of business processes — every time a person or team starts or modifies a process, they can also create one of these programs to run the process. These programs need to be easy to assemble and easy to modify11, built from a standardized, robust library of capabilities and UI components. Anyone should be able to build this type of program, either by writing small units of code where needed or by finding existing capabilities from third-party developers12, and they should be able to build at the speed of thought.
Role of the OS
The OS underlying feature-less software would have to provide three main things:
First, a relational database. In order for different programs to offer composable capabilities (which is necessary for rapidly assembling sotware), data storage has to come with more structure than what file systems offer today. The OS needs to host a canonical set of data models, each with a well-defined set of fields and the semantic meaning of each field. Programs can register custom types of data models with the OS or add custom fields to existing data models, with some control over visibility (i.e. which other programs are able to see those custom data models and fields).
Second, an action dispatcher. Feature-less software define actions, which are reified functions that do some work on inputs consisting of well-known data types. Actions return or output well-known data types as well. These functions are exposed to the OS and are the only way the OS can invoke the functionality provided by a program. Making these actions explicit enables composition:
- They can be arranged in a sequence, like pipes in shell scripting. Since actions are pre-defined, such sequences can be statically checked for unhandled behavior.
- They can be wrapped in other actions, such as a server.
They can be recorded, which enables a clear history of actions taken on a computer. This enables a variety of interesting use cases:
- Global undo/redo, potentially including the ability to undo changes non-linearly.
- Upon receiving an error or unexpected output, the ability to fix the input and replay an action.
- Built-in auditability, either for security purposes or to just see “how did I do that?”.
- User automation by saving, parameterizing, and replaying the recording.
The OS provides a standard, built-in interface for users to dispatch actions, display and store results, and browse the history of action executions. This way, a lot of programs that just exist to automate some functionality may not need to spend any effort building bespoke UIs; they can simply define their functionality as actions and use the OS’s built-in UIs.
Third, an extensive standard library consisting of the functionality that modern software commonly use. Sending an email, saving data to cloud storage, extracting text from an image and syncing data with collaborators at various levels of granularity and latency, and other actions should be as easy as making a syscall.
Developing programs in terms of granular capabilities and having an OS-level action dispatcher enables at least one novel business model: action dispatches and data-model access can be recorded and read by programs to enable usage-based billing. This doesn’t just apply to charging end-users for the actions they use; it could also be used in a B2B context on data model access. For example, an audio transcription program could add a custom field to the OS’s
CalendarEvent data model to store the textual transcript of a meeting recording and charge other applications for accessing that field.
I’m not sure that a future with feature-less software is inevitable (or even likely), but I do think the benefits are potentially so great that it’s worth trying to get there.
Modern software is incredibly complicated for developers to work with, and at the same time incredibly limiting for users — two factors that combine to make it incredibly hard to develop software that’s well-fitted to each individual use case. I believe this holds us back from realizing the full potential of computers as tools for organizing work and exploring new ideas; users instead have to work around the limitations of existing software.
I believe that both problems (the complexity facing developers and the constraints facing users) arise from the same set of reasons, listed above:
- Modern software is developed without a clear framework for reasoning about functionality, which limits both the understanding of a particular piece of software as well as the ability to share and compose functionality; and,
- The necessity of building an interface for every feature, which a) requires many developers to do a lot of work that, if not duplicated, at least rhymes, and b) limits the ability to share and compose functionality.
I think feature-less software, as I’ve defined it here, both alleviates these problems and is a viable vision for what software can look like.
If you see a text field, you’d expect, for example, to see your typing in it reflected immediately and your Backspace and arrow keys to do the right thing. But there are many more nuances in how text fields should work. If these nuances were all codified somewhere, such a listing could represent a “standard library” of text field functionality. No such listing exists.↩
For example, iOS’s Silence Unknown Callers feature didn’t ship until iOS 13; I remember wishing for a way to do that for years before it finally shipped. From a data-models-and-actions perspective, this could be thought of as a gap at intersection of a “phone call” data model and a “should alert?” action.↩
For example, some apps (like Slack and Notion) have slash-command menus, which offer a set of features found nowhere else in the UI.↩
For example, sometimes you can right-click on things to bring up a useful menu. Other times, you can right-click on things to bring up a generic menu. And occasionally, right-clicking doesn’t do anything at all.↩
For example, word processors including Word, Google Docs, and Pages support paragraph styles to enforce a consistent appearance for headings, body text, and other types of content in a document. But many people don’t know about or how to use this feature, and instead compose basic formatting features (toggling bold or italics, setting text color, etc) to achieve the end result of changing the appearance of different types of content in a document.↩
One recent example from my experience is sorting columns in Google Sheets. I wanted to sort a sheet by a particular column, but that change also synced to other people on that sheet and it was disruptive to what they were doing. I think I could’ve worked around this by creating a filter instead, but that’s a more complicated workaround (i.e. “filters” are a different, more complicated feature compared to sorting a column).↩
For example, Lightroom has a great feature to create panoramas, but I can’t use it directly on photos in my iCloud Photo Library. I have to export photos from Photos.app, import them into Lightroom, create the panorama, export the result, and re-import the result into Photos.app.↩
I think Slack was (one of) the first apps to introduce the
:emoji-name:syntax for typing out emojis. Some other apps have copied this feature, but it’s far from universal and there’s no mechanism for me to make it universal across text fields on my “personal” computer.↩
For example, Lyft has one app for riders and another app for drivers with different UIs and features. Professional apps, such as Lightroom and DaVinci Resolve, have different UI “modules” corresponding to different workflows in the professional process.↩
It should take approximately the same amount of time to assemble such a program as it would take to write documentation around a process … at least within, say, a 1×–5× difference (i.e. it might take an hour to document a process and half a workday to create the program to run the process). For most teams today, the status quo is probably a difference between 10×–100× longer.↩
This would be something like a capability “App Store”, where the offerings are probably the approximate granularity of browser extensions — generally smaller than a whole “app”, but bigger than an NPM module.↩