All questions

Styled Text Ranges

Premium

Styled Text Ranges

A rich-text editor stores its content as plain text plus a list of style ranges — "characters 0 through 5 are bold," "characters 3 through 8 are italic." When the user selects part of the document and copies it, the editor has to carry the styling along: the copied fragment must stay bold where it was bold, italic where it was italic, with the highlighting lined up over exactly the same letters. Your job is to implement that copy: a slice that cuts a window out of a styled document and produces a new, smaller document whose styling is correctly realigned to the cut text.

Styles are stored as half-open intervals [start, end)start is included, end is excluded, so a range [0, 5) covers characters at indices 0, 1, 2, 3, 4. Ranges may overlap freely: bold [0, 5) and italic [3, 8) both apply to characters 3 and 4. The slice has to keep every style that touches the window, trim the ones that hang over an edge, drop the ones that fall entirely outside, and shift all the survivors so their coordinates count from the start of the new text rather than the old.

Signature

type Range = { start: number; end: number; style: string }; // half-open [start, end)
type Doc = { text: string; ranges: Range[] };

// Cut the window [start, end) out of doc. Returns a NEW Doc:
//   - text  = doc.text.slice(start, end)
//   - ranges = each original range intersected with [start, end),
//              then shifted left by `start` (subtract start from both ends),
//              dropping any range that ends up empty.
// doc is never mutated.
function slice(doc: Doc, start: number, end: number): Doc;

Examples

// A slice through two OVERLAPPING ranges. Both survive, clamped and offset.
const doc = {
  text: 'abcdefghij',
  ranges: [
    { start: 0, end: 5, style: 'bold' },   // covers a b c d e
    { start: 3, end: 8, style: 'italic' }, // covers d e f g h
  ],
};
slice(doc, 2, 7);
// → {
//     text: 'cdefg',                       // doc.text.slice(2, 7)
//     ranges: [
//       { start: 0, end: 3, style: 'bold' },   // [0,5)∩[2,7)=[2,5) → −2 → [0,3)
//       { start: 1, end: 5, style: 'italic' }, // [3,8)∩[2,7)=[3,7) → −2 → [1,5)
//     ],
//   }
// A slice that DROPS one range and CLAMPS another.
const doc = {
  text: 'hello world',
  ranges: [
    { start: 0, end: 3, style: 'bold' },  // "hel" — fully before the window
    { start: 6, end: 11, style: 'link' }, // "world" — overlaps the right edge
  ],
};
slice(doc, 3, 8);
// → {
//     text: 'lo wo',                       // doc.text.slice(3, 8)
//     ranges: [
//       { start: 3, end: 5, style: 'link' }, // [6,11)∩[3,8)=[6,8) → −3 → [3,5)
//     ],                                     // bold [0,3) is fully before [3,8) → dropped
//   }

Notes

  • Half-open intervals. [start, end) includes start, excludes end. A range [3, 6) covers three characters: 3, 4, 5. This is the same convention as String.prototype.slice'abcdef'.slice(3, 6) === 'def'. Getting the off-by-one right at the boundaries is the crux of the problem.
  • Ranges may overlap. Two or more ranges can cover the same characters (bold and italic). They are independent; the slice processes each on its own and never merges them.
  • Drop empty ranges. A range whose intersection with the window is empty (start === end after clamping) carries no characters and must not appear in the output. This includes zero-length ranges that were empty in the input.
  • No mutation. Return a brand-new document with brand-new range objects. The caller's original doc, its ranges array, and every range object inside it must be untouched.
  • Preserve order. The surviving ranges should appear in the same relative order they had in the input.
  • Don't worry about validating inputs (assume 0 ≤ start ≤ end ≤ doc.text.length), merging adjacent same-style ranges, or any insert/delete editing operations — those are out of scope (see the solution's Going further).

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium