Longest consecutive sequence

Put everything in a set, then only start counting from a value whose predecessor is absent. That single check is what keeps it O(n): every run is walked exactly once, from its start, so the inner loop across the whole input does n steps in total rather than n per element.

Overview

The problem

Given an unsorted array of integers, find the length of the longest run of consecutive numbers. The numbers need not be adjacent in the array — only consecutive in value.

InputLongest runLength
[100, 4, 200, 1, 3, 2]1, 2, 3, 44
[0, 3, 7, 2, 5, 8, 4, 6, 0, 1]0..89
[]—0
[5]51
[1, 1, 1]11 — duplicates do not extend
[-3, -2, -1, 5]-3, -2, -13

The obvious solution is to sort and scan, which is O(n log n). The question is almost always asked with the constraint "do it in O(n)", and that constraint is the whole exercise.

Dicts, sets & hashingCoding problemMedium

Step through it

What to watch

  • Values with a predecessor present are skipped without any work.
  • Each run is walked exactly once, from its lowest member.
  • The input is never sorted.

Say this out loud

"Set for O(1) membership, then for each value check whether value-1 is in the set. If it is, skip - something else starts that run. If it isn't, walk forward. Every element is visited at most twice, so O(n)."

Longest consecutive sequence

Find the length of the longest run of consecutive integers in an unsorted array, in O(n).

Run it

The O(n) version with its inner-loop steps counted against the version missing the start check, on an input built to make the difference obvious.

1Python
Output
2Python
Output
3Python
Output

The sorting solution, for reference

4Python
Output

O(n log n), correct, and worth stating first because it establishes the baseline. The set() matters: without it, [1, 1, 2] counts the repeated 1 as extending the run.

The O(n) solution: start only at run starts

Put everything in a set for O(1) membership tests. Then for each number, only begin counting if it is the start of a run — that is, if num - 1 is not in the set.

5Python
Output

This looks quadratic — a while loop inside a for loop — and it is O(n). The reason is the continue guard:

Every number is walked over by exactly one inner loop: the one starting at the beginning of its own run. A number in the middle of a run is skipped immediately by the guard, and is visited once as part of its run's single expansion. So the total inner-loop work across the whole outer loop is n, not n per iteration.

Remove the guard and it genuinely becomes O(n²) — [1, 2, 3, ..., n] would expand from every position. That single if is the difference between the intended solution and a slow one, and explaining it is what the interviewer is listening for.

Tracing [100, 4, 200, 1, 3, 2]

numnum-1 present?ActionRun found
1No (0 absent)Expand: 2, 3, 4 present4
2Yes (1 present)Skip—
3YesSkip—
4YesSkip—
100NoExpand: 101 absent1
200NoExpand: 201 absent1

Six outer iterations, and the inner loop ran a total of six times. The answer is 4.

Iterating over seen rather than nums is a small extra win — duplicates are already collapsed, so [1] * 1000 does one iteration instead of a thousand. Either is O(n); the set is tidier.

Why not just sort

Sorting makes it trivial — one pass counting adjacent runs — and costs O(n log n). The question asks for O(n) specifically to rule that out, so give the sorting answer, name its cost, and then improve it.

The check that makes it linear

Without the start-of-run test, each value walks its whole run and the work is quadratic on a long sequence. With it, a value only walks forward when value - 1 is absent — and each run has exactly one such value.

So across the entire input the inner loop takes as many steps as there are elements, not as many as elements times run length. Every element is touched at most twice: once in the outer loop, once by the walk of its own run.

Saying the complexity convincingly

This is the question where candidates write the right code and then call it O(n²) because there is a loop inside a loop. The argument to make out loud is amortised: the inner loop's total work across all iterations is bounded by n, because runs do not overlap.

Same shape as the sliding window, where both pointers only move forward. A nested loop is not automatically quadratic; what matters is how many times the inner body can run in total.

The union-find alternative

Consecutive numbers can be treated as edges in a graph and merged with a disjoint-set structure, taking the size of the largest component.

6Python
Output

Correct, and the wrong answer for this problem — much more code, more memory, and no better asymptotically. Worth knowing only because interviewers sometimes ask "could union-find solve this?", and the right response is "yes, and the set approach is simpler for the same complexity".

ApproachTimeSpaceVerdict
Brute force per elementO(n³)O(1)No
Sort and scanO(n log n)O(n)Acceptable baseline
Hash set with run-start guardO(n)O(n)The answer
Union-findO(n α(n))O(n)Correct, overkill
Dictionary of run lengthsO(n)O(n)Also O(n), fiddlier

The dictionary of run lengths variant is worth a mention: as each number arrives, look up the run lengths ending at num-1 and starting at num+1, merge them, and update the boundary entries. It handles a stream of numbers, where the set approach needs all the input up front. That is the answer to "what if the numbers arrive one at a time?"

Edge cases

CaseResult
Empty array0 — max() over nothing raises, so guard it
Single element1
All duplicates1
Negative numbersWork unchanged — nothing assumes non-negative
Already consecutivelen(set(nums))
No two consecutive1
Very large valuesFine — the set is keyed on value, not indexed by it

That last row is the reason a hash set is used rather than a boolean array: [1, 1000000000] would need a billion-entry array and needs a two-entry set.

The empty-array case is the one that actually breaks code, because best = 0 initialised before the loop handles it, while a max(...) generator expression over an empty input raises ValueError.

Longest increasing subsequence. Superficially similar and much harder — O(n log n) with patience sorting, and no hash-set shortcut, because the values need not be consecutive.

Missing ranges / summary ranges. Compress a sorted array into range strings, which is the same run-detection logic.

First missing positive. Also solved with a set, or in O(1) space by using the array itself as a hash table.

Longest consecutive sequence in a binary tree. Same definition, traversed recursively along parent-child paths.

Longest harmonious subsequence. Values differing by at most 1, solved with a Counter.

What it is testing

Do you find the O(n) approach? The sorting solution is the baseline, and the question is asked to see whether you get past it.

Can you explain why the nested loop is linear? This is the real question. Each number is visited by exactly one expansion because non-starts are skipped.

Do you handle duplicates? A set handles them for free; a naive scan double-counts.

Do you guard the empty input? One line, and it is where the solution crashes otherwise.

Do you recognise the amortisation argument? Recognising that total work, not per-iteration work, determines complexity is a transferable skill — the same reasoning justifies the two-pointer and monotonic-stack patterns.

Recap in one screen

  • Put the numbers in a set for O(1) membership tests.
  • Expand a run only from numbers with no predecessor in the set — that guard is what makes it O(n).
  • Total inner-loop work is n, because each number belongs to exactly one run expansion.
  • Sorting gives an easy O(n log n) baseline; state it, then improve on it.
  • Guard the empty input, and use a set so duplicates cannot inflate a run.

How the code works

The O(n) version with its inner-loop steps counted against the version missing the start check, on an input built to make the difference obvious.

How the code works

  1. if v - 1 in pool: continueThe line the whole complexity rests on. A value with a predecessor is somewhere in the middle of a run that another value will walk, so doing anything here is pure duplication.
  2. while v + length in pool:A nested loop that is not quadratic. Runs do not overlap, so the total number of inner steps across the whole outer loop is bounded by n.
  3. pool = set(values)Membership has to be O(1) for any of this to work. On a list the same code would be O(n²) at best.
  4. for v in poolIterating the set rather than the list also removes duplicate work when the input repeats values.

Change one thing

  • Iterate values instead of pool on an input with many duplicates. Same answer, more work.
  • Return the run itself rather than its length by remembering v when best improves.

Where this runs

Real CPython, compiled to WebAssembly and running on your own machine — nothing is uploaded. The first run takes a few seconds while the interpreter downloads; after that it is immediate. Need more room, or want to paste your own attempt? Use the Python compiler.

Check yourself

0 of 3

Answer without scrolling back up.

  1. What makes the solution O(n) despite a loop inside a loop?

  2. How do you know a value starts a run?

  3. Why is sorting not the accepted answer?

Cheat sheet

Longest consecutive sequence

Put everything in a set, then only start counting from a value whose predecessor is absent. That single check is what keeps it O(n): every run is walked exactly once, from its start, so the inner loop across the whole input does n steps in total rather than n per element.

INTERVIEW · vizlearn.in/interview/longest-consecutive-sequence.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.