Reported July 2025
Notiontree

JSON Block Tree Operations

Reported by candidates from Notion's online assessment. Pattern, common pitfall, and the honest play if you blank under the timer.

Get StealthCoderRuns invisibly during the live Notion OA. Under 2s to a working solution.
Founder's read

Notion reported this one in July 2025, and the data structure is the whole story: an ordered forest, stored as nodes with ordered child lists. If your OA invite lands in the next couple of days, this is a design-flavored tree problem dressed up as a document editor. You get ADD, DELETE and RENDER on blocks with parents, and you return the RENDER outputs. Nothing exotic. The risk is sloppy bookkeeping, not hard algorithms. StealthCoder is the safety net if you blank on the live OA, but the pattern here is simple enough that you should walk in with the plan already in your head.

The problem

Maintain an ordered forest of document blocks. Process these operations:
["ADD", id, parentId, text] adds a block. A parentId of - makes it the newest root; otherwise it becomes the newest child of that active parent.
["DELETE", id] deletes that block and its entire descendant subtree.
["RENDER"] emits every active block in preorder. Each line is depth|id|text; roots and siblings retain insertion order, and lines are joined by a newline.
Return the values produced by RENDER operations in order.

Function
runJsonBlockTree(operations: String[][]) → String[]

Examples
Example 1
operations = [["ADD","a","-","Page"],["ADD","b","a","Heading"],["ADD","c","a","Paragraph"],["ADD","d","b","Text"],["RENDER"],["DELETE","b"],["RENDER"]]
return = ["0|a|Page\n1|b|Heading\n2|d|Text\n1|c|Paragraph","0|a|Page\n1|c|Paragraph"]
The first render is preorder. Deleting b also removes its descendant d while preserving sibling c.

Constraints
1 <= operations.length <= 20000.
Every added ID is globally unique and is never reused after deletion.
Every non-root parent is active when its child is added.
Every deleted ID is active.
IDs are non-empty ASCII strings containing neither | nor whitespace.
Text is non-empty and contains neither | nor a newline.
The total number of rendered block lines is at most 200000.

Reported by candidates. Source: FastPrep

Pattern and pitfall

Store each block in a hash map from id to a node holding text, parent id, and a child list in insertion order. Keep a separate ordered list for roots. ADD appends to the parent's children or to the roots. RENDER runs an iterative or recursive preorder from each root, emitting depth|id|text, and joins lines with a newline. The trap is DELETE. You must remove the id from its parent's child list or the roots list, otherwise RENDER still visits it. Also drop the whole subtree from the map, since IDs are never reused. Removing from a list is O(n), so use a linked hash set or a deleted flag and skip those nodes during render. Rendering is bounded by 200000 lines total, so a plain traversal is fine. Recursion depth could be 20000, so go iterative. If you freeze on the live OA, StealthCoder can hand you the skeleton fast.

If you see this problem in your OA tomorrow, the play is to recognize the pattern in 30 seconds. StealthCoder buys you that recognition.

If this hits your live OA

You can drill JSON Block Tree Operations cold, or you can hedge it. StealthCoder runs invisibly during screen share and surfaces a working solution in under 2 seconds. The proctor sees the IDE. They don't see what's behind it. Built by an Amazon engineer who passed his OA cold and still thinks the filter is broken.

Get StealthCoder

Related leaked OAs

⏵ The honest play

You've seen the question. Make sure you actually pass Notion's OA.

Notion reuses patterns across OAs. Built by an Amazon engineer who passed his OA cold and still thinks the filter is broken. Works on HackerRank, CodeSignal, CoderPad, and Karat.

JSON Block Tree Operations FAQ

What's the trick in the Notion JSON Block Tree problem?+

Model it as a forest with a hash map from id to node and ordered child lists. ADD appends to a parent or the roots. DELETE detaches the node from its parent's list and discards the subtree. RENDER is a preorder traversal that prints depth|id|text.

How hard is this Notion OA really?+

Easy to medium. No clever algorithm is needed. The difficulty is careful bookkeeping around delete, ordering, and output format. If you've written a tree with parent pointers before, you can finish it quickly without much stress.

Should I use recursion for the RENDER preorder?+

Be careful. With up to 20000 operations, a chain of blocks can be 20000 deep, which can overflow the stack in some languages. An explicit stack is safer. Push children in reverse order so they pop in insertion order, and track depth alongside each node.

How do I handle DELETE efficiently?+

Mark the node deleted and detach it from its parent's child list. A linked hash set or a deleted flag skipped during render avoids O(n) list removal. Since descendants are unreachable once the parent is gone, you don't need to touch them unless you want to free memory.

How do I prepare for this in 48 hours?+

Write the node class and the three operations from scratch once, then test the sample by hand. Check edge cases: deleting a root, deleting a leaf, and rendering an empty forest. Confirm the output joins lines with a newline and returns one string per RENDER.

Problem reported by candidates from a real Online Assessment. Sourced from a publicly-available candidate-aggregated repository. Not affiliated with Notion.

OA at Notion?
Invisible during screen share
Get it