# zsh-bench measures the latency you can feel, and human-bench decides whether you can feel it

> A benchmark for interactive zsh that starts a login shell in a virtual TTY, sends keystrokes to it and times the reaction, paired with a second tool that simulates a shell with chosen latencies so you can calibrate what a number means. The author used the pair to turn his own prompt project into something imperceptibly fast.

**romkatv/zsh-bench** — Benchmark for interactive Zsh

- Repository: https://github.com/romkatv/zsh-bench
- Stars: 1,052 · Forks: 38
- Language: Shell
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/romkatv-zsh-bench

## Two tools, one for measuring and one for judging

zsh-bench measures the user-visible latency of interactive zsh, covering input lag and command lag, and you can use it to benchmark your own shell. The companion tool, human-bench, measures human perception of latency when using interactive zsh, and you can use it to check how it feels to use zsh with specific latencies or to test whether you can tell a difference between 5ms and 0ms lag.

That pairing is the design and it is what makes the project more than a stopwatch. A latency number on its own is meaningless without a sense of what the number means to a person, and the author found the threshold empirically: command lag below 10ms feels just like 0ms, but anything above this value starts feeling sluggish. Those thresholds are then used to normalize the benchmark results to see what is fast and what is slow.

The author's own use of the tools is the proof that they work. He used human-bench to conduct a blind study on himself to find the maximum latencies that are indistinguishable from zero, and he used the thresholds to optimise two of his zsh projects, powerlevel10k and zsh4humans. They used to be fairly fast and are now, in his words, literally indistinguishable from instantaneous as far as human perception goes.

That is a modest but meaningful claim, and it is the claim the tool exists to support. A shell prompt that costs 30ms is invisible. One that costs 300ms is a hang. Everything between those two is where configuration decisions get made, and zsh-bench is a way to find out which side of the line you are on.

The project is MIT licensed, the primary language is Shell, the default branch is `master`, and the last push to the repository was on 2026-04-27 with the repository not archived.

## A virtual TTY and keystrokes, not a script timing a script

The method is what makes this a benchmark of an interactive shell rather than a benchmark of shell startup.

zsh-bench creates a virtual TTY and starts a login shell in it. It then sends keystrokes to the TTY and measures how long it takes for the shell to react. The example given is sending `echo hello` and `echo goodbye` to the TTY twice in quick succession and measuring how long it takes for the words to be printed.

That design captures the thing that a `time zsh -i -c exit` measurement misses entirely. The cost of a shell configuration is not a single number, it is a set of numbers depending on what you are doing at the time. A prompt that is fast to draw can be slow to update when a plugin runs a git command in the working directory, and a completion system that is fast for the first keystroke can be slow once the cache is cold. Driving a real TTY and timing reactions to real keystrokes measures all of that.

The output format is a flat list of capability flags followed by latency numbers.

```text
creates_tty=1
has_compsys=1
has_syntax_highlighting=1
has_autosuggestions=1
has_git_prompt=1
first_prompt_lag_ms=14.331
first_command_lag_ms=56.500
command_lag_ms=2.518
input_lag_ms=5.195
exit_time_ms=5.886
```

The first five fields are the detected capabilities, and they matter because they tell you what was measured. `creates_tty` says the shell creates its own TTY by invoking tmux or screen. `has_compsys` says the shell initialises the new completion system by invoking compinit. `has_syntax_highlighting` and `has_autosuggestions` say the corresponding plugins are active, and `has_git_prompt` says a git branch is displayed in the prompt.

Without those flags, the latency numbers are ambiguous, because a shell with no completion and no highlighting is going to be fast for reasons that have nothing to do with a good configuration.

## The five latencies, and what each one feels like

The documentation defines each latency in terms of what it measures and what it feels like when it is too high, which is the most useful framing available.

First prompt lag is from the start of the shell to the moment the prompt appears on the screen. Too high, you get to stare at an empty screen for some time whenever you open a terminal.

First command lag is from the start of the shell to the moment the first interactive command starts executing. Too high, you get to wait for the output of `ls` if you type it really fast after opening a terminal.

Command lag is from pressing Enter on an empty command line to the moment the next prompt appears, and the documentation notes it is the same measurement as the author's separate zsh-prompt-benchmark project. Too high, all commands appear to take longer to execute, and the slowdown may happen after you press Enter and before the command starts executing, or after the command finishes and before the next prompt appears.

Input lag is from pressing a regular key to the moment the corresponding character appears on the command line, and the test is performed when the current command line is already fairly long. Too high, keyboard input feels sluggish, as if you are working over an SSH connection with high latency.

Exit time is how long it takes to execute `zsh -lic "exit"`, and the documentation is explicit that this value is meaningless as far as measuring interactive shell latencies goes, because there is no baseline value for it and it cannot be too high.

The distinction between first command lag and command lag is the one that pays off in practice. First command lag captures the cold cost of the first interaction, which is where lazy initialisation shows up, and command lag captures the steady state, which is where a prompt plugin runs on every prompt. A configuration can be excellent at one and bad at the other, and most of the optimisation work in a shell is about moving work out of the second into the first.

## Installing and running it on your own machine

Installation is a clone.

```zsh
git clone https://github.com/romkatv/zsh-bench ~/zsh-bench
```

Running the benchmark is one command.

```zsh
~/zsh-bench/zsh-bench
```

Two requirements come with it. You need zsh 5.8 or newer, and zsh must be your login shell, because the benchmark starts a login shell and that is the configuration it is measuring.

There are two documented hazards, and both are the kind of thing that produces a confusing failure rather than an error message. If your zsh startup files start tmux, the benchmark may hang unless your tmux has a specific fix, linked from the README, for a specific tmux bug. And if your zsh startup files enable history but do not set `histignorespace`, you might find random commands in your history after running zsh-bench, because the benchmark is typing commands into the shell.

The command line options are compact.

```text
usage: zsh-bench [OPTION].. [CONFIG]..

OPTIONS
  -h,--help
  -i,--iters <NUM> [default=16]
  -l,--login <yes|no> [default=yes]
  -g,--git <yes|no|empty> [default=yes]
  -c,--config-dir <directory> [default=<zsh-bench-dir>/configs]
  -d,--scratch-dir <directory>
  -I,--isolation <docker|user>
  -s,--standalone
  -r,--raw
```

`--iters` sets the number of iterations and defaults to 16, which is a reasonable sample for a measurement that is otherwise noisy. `--login` and `--git` control whether a login shell is started and whether a git repository is present in the working directory, and the git option accepts an `empty` value as a third choice, which is how you measure a prompt with a git branch in an empty repository. `--config-dir` points at the directory of predefined configurations, and `--scratch-dir` sets where the working files go.

`--isolation` is the interesting one. With `--isolation docker` the predefined configurations are benchmarked in a container, and with `--isolation user` the benchmarking is done as the user `zsh-bench` on the host. The docker route is the one that gives you comparable numbers across machines, since it removes your own configuration from the picture.

## Predefined configs, a playground and a section on not fooling yourself

Benchmarking predefined configurations is the other mode, and it is the one that makes the results in the README meaningful.

```zsh
~/zsh-bench/zsh-bench --isolation docker -- <name> [name]..
```

The names are directories under the `configs` directory in the repository, so the catalogue of techniques, plugins, frameworks and plugin managers the author has benchmarked is literally the contents of a directory. The repository also has a `zsh-playground` directory and an `internal/` directory, plus `dbg/` and `doc/`.

The README's benchmark results section is organised by topic rather than by project, and the table of contents lists the categories: Basics, Prompt, Premade configs, Do it yourself, Cutting corners, Powerlevel10k with its instant prompt, Plugin managers, Deferred initialization, How not to benchmark, Full benchmark data, Conclusions, Responses, Debugging and validation, and an FAQ.

Two of those section names are worth pausing on. Cutting corners is about the techniques that a shell user adopts for speed and the cost of them, and Deferred initialization is the technique that moves work out of the critical path, which is the design principle behind most of the fast configurations. And How not to benchmark is the section that tells you where the measurement is invalid, which is the section a sceptical reader should look for first.

The Debugging and validation section is the other one to check. A latency benchmark that measures the wrong thing is worse than no benchmark, because it produces a number that looks like evidence. The validation section is where the author explains how to check that the measurement is real.

The Responses section is worth a mention for a different reason. It suggests the author has been running the benchmark against configurations proposed by readers, which is the shape of a project that accumulates shared knowledge rather than a private script. The full benchmark data is published too, so a reader can check the numbers rather than take the conclusions on trust.

## Conclusion

Adopt zsh-bench if you are tuning a shell configuration and you have been guessing at what slow feels like, because the paired human-bench tool gives you a reference point instead of an opinion. Do not adopt it as a CI gate on a startup file without reading the how not to benchmark section, because the measurement depends on the shell being your login shell and on what your startup files actually do. Verify first that your zsh is 5.8 or newer and is the login shell, that your startup files do not start tmux without the referenced fix, and that the numbers you compare are the same metric, since command lag and first prompt lag are not interchangeable.

## FAQ

### What does zsh-bench measure?

It measures the user-visible latency of interactive zsh: first prompt lag, first command lag, command lag, input lag and exit time, plus flags for detected shell capabilities like compsys, syntax highlighting, autosuggestions and a git prompt.

### How does zsh-bench measure an interactive shell?

It creates a virtual TTY and starts a login shell in it, then sends keystrokes to the TTY and measures how long the shell takes to react, for example by sending echo hello and echo goodbye twice in quick succession and timing when the words appear.

### What does human-bench do?

It measures human perception of latency in interactive zsh by simulating a shell with latencies you choose, so you can check how it feels and whether you can tell 5ms from 0ms. The author used it to find that command lag below 10ms feels like 0ms.

### What do I need to run zsh-bench on my own machine?

Clone the repository and run ~/zsh-bench/zsh-bench. It requires zsh 5.8 or newer and zsh must be your login shell. If your startup files start tmux you may need a specific tmux fix, and if they enable history without setting histignorespace you may find stray commands in your history afterwards.

### How do I benchmark the predefined zsh configurations?

Run ~/zsh-bench/zsh-bench --isolation docker -- <name> [name].., where the names are directories under the configs directory of the repository. With --isolation user the benchmarking is done as the user zsh-bench on the host instead.

## Sources

- [Issues](https://github.com/romkatv/zsh-bench/issues)
- [License: MIT](https://github.com/romkatv/zsh-bench/blob/master/LICENSE)
- [README](https://github.com/romkatv/zsh-bench/blob/master/README.md)
- [romkatv/zsh-bench on GitHub](https://github.com/romkatv/zsh-bench)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/romkatv-zsh-bench
