notion prep

summer 2026 · swe internship

Working through each stage of the Notion internship interview process. The main focus is the debugging virtual onsite — a large TypeScript codebase, a live Notion-like editor, and logic bugs to squash.

01 codesignal 02 phone screen 03 take-home 04 onsite 1 — algo 05 onsite 2 — debug 06 behavioral
0 / 0 tasks done

4 problems · 70 minutes · scored out of 600. Each module escalates — clear M1–M3 cleanly first, then tackle M4. Code quality is scored alongside correctness.

M1 Basic Coding ~10 min · 5–10 lines
  • Numbers, strings, basic arrays
  • Single loop at most
  • Step-by-step instructions given
  • No algorithms or pattern-spotting
M2 Data Manipulation ~15 min · 10–20 lines
  • Split, compare, reverse strings
  • Iterate, modify, concat arrays
  • Up to 2 nested loops
  • 3–5 basic concepts combined
M3 Implementation Efficiency ~20 min · 25–40 lines
  • 2D arrays — traverse, transpose, pivot
  • Hashmaps with string/int keys
  • Decompose into sub-functions
  • Must meet execution time limits
M4 Problem Solving ~30 min · 25–35 lines
  • Greedy, two pointers, divide & conquer
  • Hashmaps + sets for optimization
  • Discrete math fundamentals
  • No Dijkstra, Kruskal, FFT, or DP

Score M1–M3 first. Earlier modules carry more weight — a clean M1–M3 beats a half-finished M4.

CodeSignal scores code quality too — clean names, no debug cruft, readable structure.

The 70-minute window is real. Timed practice is the only way to build the right muscle for it.

M4 patterns: two pointers, sliding window, frequency hashmaps, binary search on the answer.

Practice GCA expires Jun 21
open →
Find Occurrences of an Element in an ArrayLC 3005
Spiral MatrixLC 54
Longest Consecutive SequenceLC 128
Spiral Matrix IILC 59
4Sum IILC 454
Rotate ImageLC 48
Pairs of Songs with Total Durations Divisible by 60LC 1010
Diagonal TraverseLC 498
Longest Palindrome by Concatenating Two Letter WordsLC 2131
Reshape the MatrixLC 566
Array of Doubled PairsLC 954
Toeplitz MatrixLC 766
3Sum With MultiplicityLC 923
Image OverlapLC 835
Count Number of Nice SubarraysLC 1248
Largest Local Values in a MatrixLC 2373
K-diff Pairs in an ArrayLC 532
Transpose MatrixLC 867
Diagonal Traverse IILC 1424
Text JustificationLC 68
Find the Number of Distinct Colors Among the BallsLC 3109
Count Square Submatrices with All OnesLC 1277
Minimum Number of Arrows to Burst BalloonsLC 452
Koko Eating BananasLC 875
Design HashMapLC 706
Complete the practice GCA before Jun 21
Do timed sessions — 70 min, no pausing — to simulate real conditions
Practice in the CodeSignal IDE specifically — the environment differs from LC
Review binary search on the answer pattern (M4 favorite)

A short call with a recruiter (usually Shivani Patel or Chris). It's mostly a vibe check — confirming you're eligible, interested, and not a robot. Non-technical. If you pass, you get the take-home.

Be yourself. This is mostly eligibility + enthusiasm, not technical screening.

Have a clear "why Notion" — they care about cultural fit and genuine interest in the product.

Know your resume and be ready to talk briefly about past work.

Ask smart questions at the end — what teams are hiring, what the timeline looks like, etc.

Draft a clear "why Notion" answer (product, culture, mission)
Re-read your resume and be ready to walk through it in 2 min
Prepare 2–3 smart questions to ask the recruiter

A take-home project, usually involving the Notion API or a small feature build. Notion recreates this each year. Spend the full 2–3 hours — readability and craftsmanship matter as much as the solution.

Readability is everything. Clean variable names, logical decomposition, comments only where necessary.

Polish the README — make sure it's well organized, explains setup, and covers edge cases you thought about.

The short-answer questions in the README are a big deal. For "what would you improve with more time?" — actually rip apart your code and be specific about tradeoffs.

Write tests. You'll regret it if you don't.

Make it actually work — test your code before submitting.

Read the Notion API docs and understand the core endpoints
Practice writing polished READMEs with setup + short answers
Review code decomposition patterns — small functions, clear separation of concerns
Build a small personal project using the Notion API for practice

A live coding interview: 45 minutes to solve a problem, 15 minutes to ask your interviewer questions. Historically involves OOP / class design and time complexity optimization. Notion creates original questions each year.

Think about time and space complexity out loud. They want to see you reason about performance.

Start with a brute force solution, articulate why it's suboptimal, then optimize. Showing the thought process matters more than jumping straight to optimal.

Past questions have involved class implementation and binary search — practice OOP design problems.

The bar isn't just solving the problem — they care about engineering fundamentals and how you communicate tradeoffs.

Related LC problem: 981. Time Based Key-Value Store — OOP + binary search.

Solve LC 981 (Time Based Key-Value Store) and variations
Practice 10+ OOP design problems (design a parking lot, LRU cache, etc.)
Practice explaining time/space complexity out loud on every problem you solve
Do at least 5 mock interviews (narrate your thinking out loud)
Review binary search patterns (lower bound, upper bound, on answer)
Prepare 3–5 smart questions to ask your interviewer at the end
You'll be given a large TypeScript codebase in CodeSignal that powers a scaled-down Notion page editor. You'll also have the live editor open so you can see changes in real time. Bugs will be shown one by one — your job is to navigate the codebase and fix them. All bugs are logic-based (off by one, wrong condition, etc.) — no HTML/CSS.

A large, multi-file TypeScript codebase you've never seen before.

A live Notion-like page editor you can interact with alongside the code.

Logic bugs only — off by one, wrong condition, missing null check, etc.

Bugs may or may not have pre-written unit tests. Ask your interviewer.

React components + backend code powering the editor.

45 minutes — time management is critical.

Ask: "Are there unit tests for this bug, or should I just navigate the codebase?"

Clarify: current behavior vs expected behavior — spell it out before diving in.

Don't be afraid to ask what Notion concepts mean — same as on the job.

console.log freely — use your debugging tools, don't just stare.

After fixing: ask if they want unit tests or a discussion of how to prevent the bug.

Over-communicate — narrate what you're looking at and why.

Watch a TypeScript crash course (syntax, types, generics, interfaces, utility types)
Read through a real large TypeScript codebase cold (Oyster/open source) and trace a feature end-to-end
Practice writing Jest unit tests from scratch (describe, it, expect, beforeEach, jest.fn())
Practice reading and extending existing Jest test suites
Find and fix "good first issues" in an open source TypeScript/React repo
Study React hooks: useState, useEffect, useCallback, useMemo, useRef
Study common logic bug patterns: off by one, null/undefined edge cases, array index issues, stale closure
Practice narrating your debugging process out loud while working through unfamiliar code
Set up Oyster locally and explore the frontend codebase
Practice the "clarify current vs expected behavior" habit before touching any code
types & interfaces
// basic types
let name: string = "youdahe"
let count: number = 0
let done: boolean = false

// interface
interface User {
  id: string
  name: string
  age?: number   // optional
}

// type alias
type Status = "active" | "inactive"

// generic
function wrap<T>(val: T): T[] {
  return [val]
}
utility types
// Partial — all props optional
type P = Partial<User>

// Pick — subset of props
type N = Pick<User, "id" | "name">

// Omit — exclude props
type O = Omit<User, "age">

// Record — key/value map
type M = Record<string, number>

// Optional chaining
user?.address?.city

// Nullish coalescing
const name = user.name ?? "anon"

// Non-null assertion
const el = document.getElementById("x")!
common array patterns
const nums: number[] = [1, 2, 3]

// map / filter / reduce
const doubled = nums.map(n => n * 2)
const evens = nums.filter(n => n % 2 === 0)
const sum = nums.reduce((acc, n) => acc + n, 0)

// find / findIndex
const found = nums.find(n => n > 1)
const idx = nums.findIndex(n => n > 1)

// spread
const copy = [...nums, 4, 5]

// destructuring
const [first, ...rest] = nums
async / error patterns
// async/await
async function fetchUser(id: string) {
  const res = await fetch(`/api/users/${id}`)
  if (!res.ok) throw new Error("failed")
  return res.json() as Promise<User>
}

// type guard
function isString(v: unknown): v is string {
  return typeof v === "string"
}

// as const
const ROLES = ["admin", "user"] as const
type Role = typeof ROLES[number]
test structure
describe("MyComponent", () => {
  let instance: MyClass

  beforeEach(() => {
    instance = new MyClass()
  })

  afterEach(() => {
    jest.clearAllMocks()
  })

  it("does the thing", () => {
    const result = instance.doThing()
    expect(result).toBe(true)
  })

  it("handles edge case", () => {
    expect(() => instance.bad()).toThrow()
  })
})
common matchers
// equality
expect(x).toBe(y)         // === (primitives)
expect(x).toEqual(y)      // deep equality
expect(x).not.toBe(null)

// truthiness
expect(x).toBeTruthy()
expect(x).toBeFalsy()
expect(x).toBeNull()
expect(x).toBeUndefined()

// arrays / strings
expect(arr).toHaveLength(3)
expect(arr).toContain(item)
expect(str).toMatch(/pattern/)

// numbers
expect(n).toBeGreaterThan(0)
expect(n).toBeLessThanOrEqual(10)
mocking
// mock a function
const mockFn = jest.fn()
mockFn.mockReturnValue(42)
mockFn.mockResolvedValue({ id: "1" })

expect(mockFn).toHaveBeenCalled()
expect(mockFn).toHaveBeenCalledWith("arg")
expect(mockFn).toHaveBeenCalledTimes(2)

// mock a module
jest.mock("../api", () => ({
  fetchUser: jest.fn().mockResolvedValue({
    id: "1", name: "test"
  })
}))

// spy on method
const spy = jest.spyOn(obj, "method")
async tests
// async/await
it("loads data", async () => {
  const data = await fetchUser("1")
  expect(data.name).toBe("youdahe")
})

// resolves/rejects matchers
it("resolves correctly", async () => {
  await expect(fetchUser("1")).resolves
    .toMatchObject({ id: "1" })
})

it("throws on bad id", async () => {
  await expect(fetchUser("")).rejects
    .toThrow("failed")
})
state & effects
// useState
const [count, setCount] = useState(0)
setCount(prev => prev + 1)  // functional update

// useEffect — runs after render
useEffect(() => {
  document.title = `Count: ${count}`
  return () => { /* cleanup */ }
}, [count])  // dep array controls when it fires

// missing dep = stale closure bug
// extra dep = unnecessary re-runs
memoization & refs
// useCallback — memoize function ref
const handleClick = useCallback(() => {
  doSomething(id)
}, [id])

// useMemo — memoize computed value
const sorted = useMemo(() =>
  items.sort(compareFn)
, [items])

// useRef — mutable, no re-render
const inputRef = useRef<HTMLInputElement>(null)
inputRef.current?.focus()

// also useful for previous value tracking

Off by one: i < arr.length vs i <= arr.length. Watch slice bounds, substring indices, loop ranges.

Null/undefined: Accessing a property on something that could be null. Look for ?.chaining that should be there but isn't.

Stale closure: An async callback or event handler captures an old value of state. Fixed by using functional updates or putting the value in a ref.

Wrong comparison: == vs ===, comparing objects by reference instead of value.

Missing return: A function falls through without returning a value in a branch.

Mutation side effects: Mutating an array/object directly instead of returning a new one — causes React not to re-render.

Wrong dependency array: useEffect or useMemo missing a dependency, so it runs on stale data.

Oyster (ColorStacks) Large TypeScript/React codebase — set up locally, find something on the frontend, trace the code
TypeScript Crash Course Any 1–2 hr YouTube tutorial — just get comfortable with the syntax and common patterns
Jest Docs jestjs.io/docs
React Docs (Hooks) react.dev/reference/react
Good First Issues goodfirstissue.dev Filter by TypeScript — fix real bugs in open source

A standard behavioral interview — usually 45 minutes with the eng manager of the team you'd be joining. Use the STAR method (Situation, Task, Action, Result) for every story. Notion cares about curiosity, collaboration, and shipping quality work.

STAR every story — Situation, Task, Action, Result. Keep it tight.

Prepare 5–6 strong stories that can flex to different questions (conflict, failure, impact, collaboration, learning).

Notion hires people who are curious and love the product — have genuine answers about why Notion specifically.

Don't be arrogant about tradeoffs — discuss them thoughtfully, not like you know better than everyone.

Asking good questions at the end matters — shows genuine interest in the team and role.

A project you're most proud of — what you built, why it mattered, what you learned
A time you failed or made a mistake — what happened, what you did about it
A time you had conflict or disagreement with someone — how you handled it
A time you had to learn something quickly under pressure
A time you pushed back on a decision or advocated for a different approach
Why Notion specifically — product you love, team culture, what you want to build
Prepare 3–5 thoughtful questions to ask the interviewer about the team / role / product