# leetcode.nvim: solving LeetCode problems without leaving Neovim

> A Lua plugin that turns Neovim into a LeetCode client, with a picker for navigation, formatted problem statements via tree-sitter, runnable test cases and injectable boilerplate.

**kawre/leetcode.nvim** — A Neovim plugin enabling you to solve LeetCode problems.

- Repository: https://github.com/kawre/leetcode.nvim
- Stars: 2,204 · Forks: 122
- Language: Lua
- License: MIT
- Published: 2026-10-07 · Updated: 2026-10-07 · Language: en
- Canonical page: https://hysenlabs.com/projects/kawre-leetcode-nvim

## The problem this solves is not the algorithm

LeetCode's web interface asks you to switch context several times per problem: read the statement in a browser, write code in a textarea with autocomplete that is not your editor, run a test case and read a result. If you live in Neovim, that round trip is the friction, not the difficulty of the problem.

leetcode.nvim moves the whole loop inside the editor. `:Leet` opens a menu dashboard, you pick a problem, and the statement appears formatted alongside the buffer you write in. Running code opens a console, submitting goes to the judge, and the result comes back in a panel rather than a page load.

The requirements are modest: Neovim 0.9.0 or newer, a picker, plenary.nvim and nui.nvim. The first two are hard requirements because they do the plugin's async work and its UI primitives. nui.nvim in particular is what makes the layout possible, and it is why the description panel, the console and the test case view can all be framed the way they are.

Optionally, tree-sitter-html is described as highly recommended, and it earns that. It is what formats the problem description so you get indented code blocks and lists instead of one long line of HTML text. A Nerd Font and nvim-web-devicons are listed as optional cosmetics.

## Installing with lazy.nvim in one block

The README's lazy.nvim snippet is short because the defaults do most of the work, and the one non-obvious field is the build step.

```lua
{
    "kawre/leetcode.nvim",
    build = ":TSUpdate html", -- if you have `nvim-treesitter` installed
    dependencies = {
        -- include a picker of your choice, see picker section for more details
        "nvim-lua/plenary.nvim",
        "MunifTanjim/nui.nvim",
    },
    opts = {
        -- configuration goes here
    },
}
```

The `build` line is the part people miss. If tree-sitter-html is not already compiled in your setup, the plugin's own `:TSUpdate html` handles it on install. Skipping that and then wondering why problem descriptions render as raw HTML is the most common first-run problem.

For picker support you name one in dependencies and then optionally pin which provider the plugin uses. Leaving `picker.provider` as nil makes the plugin resolve the first available one, in a defined order: snacks-picker, then fzf-lua, then telescope, then mini-picker.

That resolution order is a design choice worth noting. The plugin checks in order from newest to most established, which means if you have snacks installed for other reasons it wins over telescope even if telescope was your intended picker for this plugin. Setting the provider explicitly avoids the surprise.

## Twenty-one languages and a config that types itself

The language table is longer than most plugins of this kind support: C++, Java, Python, Python3, C, C#, JavaScript, TypeScript, PHP, Swift, Kotlin, Dart, Go, Ruby, Scala, Rust, Racket, Erlang, Elixir and Bash, each mapped to a short `lang` string you set as the session default.

```lua
lang = "cpp"
```

Racket and Erlang in that list are a hint about the author. This is a plugin for people who use Neovim as a daily driver and want their practice environment to match their editor keybindings, not a teaching tool.

The default configuration is annotated with LuaCATS type comments, which means your editor can complete and check it. The options you are most likely to touch:

```lua
    keys = {
        toggle = { "q" }, ---@type string|string[]
        confirm = { "<CR>" }, ---@type string|string[]

        reset_testcases = "r", ---@type string
        use_testcase = "U", ---@type string
        focus_testcases = "H", ---@type string
        focus_result = "L", ---@type string
    },
```

That maps to vim-like navigation: `q` toggles the menu, Return confirms, `r` resets the test cases, `U` uses one, `H` focuses the test case pane and `L` the result pane. The full configuration template lives at `lua/leetcode/config/template.lua`, which is the file to read when you want typed completion rather than copying the README.

There is also a `cn` block for using leetcode.cn instead of leetcode.com, with separate toggles for the translator and for translating problem statements. That is not a niche option: it is the difference between a usable plugin and an unusable one for anyone who cannot read English problem statements comfortably.

## Code injection is the feature to understand

LeetCode judges submit exactly what is in the editor box. That creates a problem for languages where a solution needs boilerplate, and for anyone who writes tests locally and does not want them in the submission.

The injector solves both. It inserts code before and after your solution that is never submitted and never run, and imports are injected at the top of the buffer and folded by default.

```lua
injector = { ---@type table<lc.lang, lc.inject>
    ["python3"] = {
        imports = function(default_imports)
            vim.list_extend(default_imports, { "from .leetcode import *" })
            return default_imports
        end,
        after = { "def test():", "    print('test')" },
    },
    ["cpp"] = {
        imports = function()
            -- return a different list to omit default imports
            return { "#include <bits/stdc++.h>", "using namespace std;" }
        end,
        after = "int main() {}",
    },
},
```

The Python example is the common case: you keep your local test scaffold in the buffer, exercise your solution with real calls while you work, and submit without any of it. The C++ example shows the other half of the API, where returning a fresh list from `imports` omits the defaults entirely rather than extending them.

The related editor options are `reset_previous_code`, which defaults to true and clears the previous solution when you enter a new problem, and `fold_imports`, also true. Both are there because the plugin wants the buffer to look like a fresh file rather than an accumulation of attempts, which is the right default for practice and the wrong one if you are using it to keep notes across problems.

Three hooks, `enter`, `question_enter` and `leave`, take lists of functions. They are the extension point for anything the plugin does not do for you, such as opening a scratch buffer per problem or logging your solve times somewhere of your own.

## Caching, sessions and what the releases tell you

Problem data is cached with an update interval of seven days by default, which is what keeps the dashboard responsive. You control the two directories it uses:

```lua
    storage = {
        home = vim.fn.stdpath("data") .. "/leetcode",
        cache = vim.fn.stdpath("cache") .. "/leetcode",
    },
```

Caching means you can open the problem list without a round trip each time, and it means the list can be briefly out of date, which for a practice tool is the right trade.

Sessions arrived in v0.2.0 along with `Leet session change name=...`, `Leet session create name=...` and `Leet session update`, plus a footer showing daily streak and session statistics. That release also added better premium indicators for premium users and a `Leet restore` command to fix a layout that has drifted.

Version 0.3.0 added snacks.nvim and fzf-lua picker support. Version 0.3.1 fixed a pluralisation bug when one problem was solved and added snacks support properly, with three named contributors credited for the changes.

Here is the thing to be aware of. The newest tag is v0.3.1 from 2025-06-28, while the branch has been pushed to as recently as 2026-04-28. So the code in the repository is well ahead of the last release, and installing with a plugin manager gives you main, not a tagged version. That is normal for this plugin and its ecosystem, but it does mean there is no stable point to pin to if you need one.

Commands beyond `:Leet` include `menu`, `exit`, `console` for a pop-up console on the currently open question, and `info` for a pop-up with details.

## What it costs you, and what it asks of your setup

Two dependencies and a parser is not nothing, but it is small next to what the plugin does. The larger consideration is what you are signing into.

LeetCode account credentials have to reach the API for your submissions, stats and solved-problem list to work. Any plugin in this category is asking for your account, and the trade is that you get your practice inside your editor instead of a browser. The README does not discuss credential handling, so if that is a deciding factor for you, the code in `lua/` is where to look, and it is only a Lua tree with no vendored binaries.

The second consideration is that LeetCode is a paid problem set with a subscription tier, and premium indicators in the UI are a reminder that the free tier is a subset. Nothing in the plugin changes that.

The tree itself is minimal: `lua/`, `LICENSE`, `README.md`, `.stylua.toml` and `.neoconf.json`. There is no `plugin/` directory, no test suite and no CI configuration in what is visible here, which for a plugin of this size is both a sign of a focused codebase and a sign that you are relying on the author's testing.

Image support is the last thing to decide about. It requires image.nvim 1.4.0 or newer, specifically because that version fixed text wrapping, and it is off by default. Problems with diagrams need it; most do not, and it is the kind of dependency worth adding only when a problem forces the issue.

The licence is MIT, and the last push was 2026-04-28 with 2,204 stars and 66 open issues, so the open issue count relative to stars is high enough that you should expect to read GitHub rather than a manual when something does not work.

## Conclusion

leetcode.nvim earns its place by treating the Neovim buffer as the working surface rather than an external editor it shells out to. The parts that make that work are the same ones you would build yourself with effort: a picker-aware dashboard, tree-sitter formatting so problem text is readable, a console that opens on runcode, and an injector that keeps your boilerplate out of the submitted file. Two things to check before committing. The latest tagged release is v0.3.1 from 2025-06-28 while the branch has moved since, so you are installing from source on main rather than from a release. And LeetCode account credentials are a real consideration for a plugin that talks to a paid problem set. Install it with lazy.nvim, set your language, bind a leader key to Leet, and leave image_support off until you actually need it.

## FAQ

### What does leetcode.nvim do?

It brings LeetCode into Neovim. `:Leet` opens a menu dashboard, picking a problem shows the formatted statement next to the buffer you write in, and running and submitting happen in console panels inside the editor rather than in a browser.

### Which languages does leetcode.nvim support?

Twenty-one of them, set with a short `lang` string: C++, Java, Python, Python3, C, C#, JavaScript, TypeScript, PHP, Swift, Kotlin, Dart, Go, Ruby, Scala, Rust, Racket, Erlang, Elixir and Bash. The default session language is C++.

### Why do problem descriptions need tree-sitter?

tree-sitter-html is listed as optional but highly recommended because it formats the problem statement, turning HTML into indented code blocks and real lists. Without it, descriptions render as flattened text. The lazy.nvim install spec handles it with a `:TSUpdate html` build step.

### How does leetcode.nvim keep my test code out of submissions?

The injector inserts code before and after your solution that is never submitted and never run, with imports folded at the top of the buffer. The Python example keeps a local `def test()` scaffold in the buffer while the judge only sees the solution.

### Which picker does leetcode.nvim use?

Whichever you name, or the first available of snacks-picker, fzf-lua, telescope and mini-picker, in that order, when `picker.provider` is nil. That ordering means snacks wins over telescope if both are installed, so pin the provider explicitly if you have a preference.

## Sources

- [Issues](https://github.com/kawre/leetcode.nvim/issues)
- [kawre/leetcode.nvim on GitHub](https://github.com/kawre/leetcode.nvim)
- [License: MIT](https://github.com/kawre/leetcode.nvim/blob/master/LICENSE)
- [README](https://github.com/kawre/leetcode.nvim/blob/master/README.md)
- [Releases](https://github.com/kawre/leetcode.nvim/releases)

---

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