Complexity analysis
Time and space complexity estimates for every file and function.
Code that works on your test data can still slow to a crawl on real data. The Complexity Dashboard estimates the time and space complexity (Big-O) of every file and function in your project, so you can find the costly patterns before your users do.
Run an analysis
- Open a folder.
- Open View → Complexity Dashboard, or its icon in the activity bar.
- On the Complexity tab, click Run complexity analysis. Network and database calls have their own tab and button: see Network analysis.
Results appear file by file as they're ready. The line under the button says how far the analysis got, for example 25 of 256 files analyzed · 90 ms · 18 unchanged: files whose results were reused because they haven't changed since the last run are counted as unchanged. Cancel stops a run.
Large projects: 25 files at a time
A run doesn't analyze the whole project at once. It takes the first 25 files, in folder and file-name order, and stops. The bar at the bottom of the panel shows how far it got and offers the next batch:
Showing 25 of 256 files · Load 25 moreScroll to the end of the list, or click the bar, and the next 25 files are analyzed and added below. Files you never scroll to are never read, so the first results are ready in milliseconds however big the project is, and scrolling stays smooth: new cards are only ever added below the ones you're reading.
Each batch is sorted worst first within itself. Because a file has no score until it's been analyzed, the worst file in the whole project may be in a later batch. To rank everything at once, ask the assistant (see below): it analyzes the whole workspace in one go.
Complexity analysis is free: it runs entirely on your computer with no AI and no internet.
Reading the results
Each file is a card showing:
- its name and folder, and a score colored green, amber or red by how much attention it needs;
- T: its worst time complexity, for example
O(n·m); - S: its worst space complexity.
Click a card for the details:
| Section | Shows |
|---|---|
| Why | The function that makes the file costly, and the lines behind it: each loop with how many times it runs, and each costly call. |
| Functions | Each function that isn't constant-time, with its line range and its own time and space complexity, plus how many constant-time functions there are. |
| Footer | That this is a static estimate, its confidence (low, medium or high), and which engine produced it. |
What n and m stand for
Each letter is the size of something in your code, named n, m, k… in the order they appear, and the Why section says which is which. For this function:
function totalsByUser(users, orders) {
const totals = [];
for (const user of users) {
const mine = orders.filter((order) => order.userId === user.id);
totals.push({ user, total: mine.reduce((sum, order) => sum + order.amount, 0) });
}
return totals;
}Time O(n·m): totalsByUser() at line 1.
where n = |orders|, m = |users|
Line 4: `orders.filter()` costs O(n)
Line 5: `mine.reduce()` costs O(n)
Line 3: `for (const user of users)` runs m timesO(n·m) means the work grows with the number of orders times the number of users: every user scans every order. Grouping the orders by user in a Map first brings it down to O(n + m), orders plus users.
A size marked (per element) is the size of one item, such as one row of a table. Going through every row and every cell in it is still O(n) in the data, not O(n²).
Confidence
| Confidence | Means |
|---|---|
| High | Every bound in the result was worked out from the code. |
| Medium | Some calls were assumed to be constant-time without knowing them (listed as assumed O(1)), usually calls into other files or packages, or some functions in the file have no bound. |
| Low | Part of the result is unknown. |
How the estimate is worked out
For TypeScript, JavaScript, Python, C and C++, Lamied's native engine works out each function's cost from its syntax tree:
- Loops run as many times as what they go through:
for (const x of xs)runs once per item ofxs, andfor (let i = 0; i < n; i++)runsntimes. A loop that halves or doubles its counter, as binary search does, runslog ntimes, and a fixed limit such asattempt < 3counts as constant. Callbacks to.map,.filter,.forEachand the like, and Python comprehensions, are loops over their collection. A loop inside a loop multiplies. - Library calls are priced by what they do: sorting is
n log n;includes,indexOf,filterandjoingo through the whole collection;push,Map.getandSet.hasare constant. In C++, a lookup depends on the container's declared type: linear in avector,log nin amaporset, constant in anunordered_map. - Your own functions: a call to another function in the same file costs what that function costs, with your arguments in place of its parameters.
- Recursion is solved from how the input shrinks. Shrinking by one each call (
n - 1,xs.slice(1)) is linear, or exponential when the function calls itself twice, like naive Fibonacci. Halving, as in binary search or merge sort, giveslog norn log n. Visiting a tree's children is linear in its nodes, and a memoized function counts each subproblem once. - Space is the memory that's kept, such as a list you add to inside a loop, plus the depth of any recursion.
- Code outside functions, such as a script or module setup, is analysed too and shows as top-level code when it grows with its input.
When the answer is unknown
When no bound can be worked out, the result is unknown with the reason, never a guess. The most common reasons:
- a loop with no limit to derive, such as a
while (true)loop that waits for events; - functions that call each other in a cycle;
- recursion inside a loop;
- recursion that shrinks its input in a way it doesn't recognise, such as quicksort on filtered copies;
- code nested too deeply to analyse, which is usually generated code.
The Why section counts these functions and names the first one, with its line and the reason. They don't make the whole file unknown: one event loop doesn't hide the cost of everything else. They lower the file's confidence to medium.
Supported languages
| Languages | How they're analysed |
|---|---|
| TypeScript, JavaScript, Python, C, C++ | Lamied's native engine, as described above |
| Java, C#, Go, Rust, Kotlin, Swift, PHP, Scala, Dart | Structural heuristics: loop nesting and recursion, with every loop counted as n |
| Ruby | A simple keyword count, marked low confidence |
A .h header is analysed as C++ when its content is C++ (classes, templates, namespaces, std::) and as C otherwise. Each result states its own confidence, and the footer says which engine produced it, so you know how much weight to give it.
Explain with AI
On any file's details, Explain with AI (Pro) sends that one file and its analysis to your AI model and streams back two sections:
- Complexity: confirms or corrects the estimate, function by function.
- Recommended data structures & algorithms: concrete swaps, such as a linear scan → a hash-map lookup, or repeated sorting → a heap.
The answer stays on the card while the file is unchanged. Ask again gets a fresh one, for example after you edit the file. The AI is only called when you click; running an analysis never uses it. Network and database calls are explained on the Network tab: see Network analysis.
Hand it to the assistant
Add to AI chat puts the file's analysis (and the AI explanation, if you have one) into the chat box. Add your request, for example “rewrite the hot function to use a Map”, and send. You can also just ask the assistant, “which files have the worst complexity, and why?”; it runs the same analysis itself, over every file at once, and ranks the worst across the whole project.
What's analysed
- Every supported source file in the workspace, 25 at a time as you scroll.
- Not: dependency and build folders (
node_modules,vendor, virtual environments,coverage…), hidden folders, symbolic links, or anything in.lmignore(see Codebase indexing). - Files over 512 KB are skipped and counted in the summary.