15 min read

L1–L2 · Logic and TypeScript

The bugs you can see without running anything: one wrong line, or one input nobody imagined.


The story

A subscription service bills customers on the same day each month. It has run without a complaint for a year. Then, in early March, finance notices that customers who signed up on January 31 were never billed in February, and their billing day has quietly moved to the 3rd. Months later the same thing happens to customers who signed up on August 31: no charge in September, and a new billing day of the 1st.

The code is three lines:

ts
const next = new Date(lastBilled)
next.setMonth(next.getMonth() + 1)
return next

It's correct for 28 days of every month, which is why every test passed. setMonth doesn't clamp: January 31 plus one month is "February 31", which JavaScript rolls over to March 3 (or March 2 in a leap year).

No concurrency, no infrastructure, no attacker. Just one line and one input the author didn't picture. L1 and L2 bugs are the most common in any codebase, and the ones interviewers expect everyone to catch. The skill is building reflexes: a short list of questions you ask automatically of every line and every function.


Part 1: L1 reflexes — questions for every line

AskBecause
Is this the right variable?Copy-paste twins: start where end was meant
Is the boundary right?< vs <=, slice(0, n - 1), "up to" vs "fewer than"
What if this is 0, "", null or undefined?Truthiness checks and || defaults misfire on valid falsy values
Does it return / await what it computes?Arrow functions with braces, missing await
Does it change something it was given?sort, push, splice and property writes on inputs
What type is this really at runtime?Query params and env vars are strings. JSON dates are strings.
What unit is this?Seconds vs milliseconds, rupees vs paise
Does === mean what it looks like here?Objects, arrays and Dates compare by reference

The drills below each target one family from the catalogue. Ask the reflex questions on every line.

Drill 1: Arrays

Bug hunt
Cart utilities
5 bugs planted. Click every line you think is wrong, then check.
0:00 / 3 min

Drill 2: Boundaries and comparisons

Bug hunt
Leaderboard
4 bugs planted. Click every line you think is wrong, then check.
0:00 / 3 min

Drill 3: References

Bug hunt
User settings
3 bugs planted. Click every line you think is wrong, then check.
0:00 / 3 min

Drill 4: Strings

Bug hunt
Text helpers
5 bugs planted. Click every line you think is wrong, then check.
0:00 / 3 min

Drill 5: Numbers and money

Bug hunt
Invoice maths
5 bugs planted. Click every line you think is wrong, then check.
0:00 / 4 min

Part 2: L2 reflexes — inputs nobody pictured

L2 bugs hide in functions that are right for the example in the author's head. For every function, run through this list of inputs:

Then three more questions:

  • State: is every transition allowed from every state it can be called in?
  • Errors: on the failure path, what's half-done, what's swallowed, what's retried?
  • Types: where does TypeScript believe something it never checked (as, !, any, JSON.parse, index lookups)?

Drill 6: Time

Bug hunt
Billing dates
5 bugs planted. Click every line you think is wrong, then check.
0:00 / 4 min

Drill 7: State

Bug hunt
Order status
5 bugs planted. Click every line you think is wrong, then check.
0:00 / 4 min

Drill 8: Error handling

Bug hunt
Profile sync
5 bugs planted. Click every line you think is wrong, then check.
0:00 / 5 min

Drill 9: TypeScript holes

Bug hunt
Plans and permissions
5 bugs planted. Click every line you think is wrong, then check.
0:00 / 5 min

Drill 10: Configuration

Bug hunt
App config
4 bugs planted. Click every line you think is wrong, then check.
0:00 / 3 min

Drill 11: Resources

Bug hunt
Leaks
3 bugs planted. Click every line you think is wrong, then check.
0:00 / 4 min

Cheat sheet

ReflexCatches
"What if it's 0, "", null?"Falsy checks, || defaults, missing null checks
"Does it return or await what it computes?"Arrow-with-braces, missing await, return without await in try
"Does it change what it was given?"sort, push, shallow spreads, shared defaults
"What type is this at runtime?"Env vars, query params, JSON dates, as casts
"What unit?"ms vs s, paise vs rupees, 0- vs 1-indexed months
"Empty, one, max, negative, duplicate?"Edge-case crashes and wrong totals
"31st, DST, another zone?"Calendar and time-zone bugs
"Which states can call this?"Illegal transitions, missing switch cases
"What's half-done if this throws?"Partial updates, leaked connections, swallowed errors
"What is TypeScript believing?"any, as, !, Record lookups, lying guards

Say it in the interview

"Line 3: setMonth overflows, so a customer billed on January 31 is next billed on March 3. Any subscription started on the 29th to 31st drifts. I'd clamp to the last day of the target month, and add tests for the 31st, February in leap and non-leap years, and a DST boundary."


Go deeper