PracHub
QuestionsPremiumCoachesLearningGuidesInterview Prep

Quick Overview

This question evaluates event-driven simulation, time-ordered state management, floating-point numerical evaluation of aggregate functions, and data-structure design for tracking dynamic object states and stability thresholds; it falls under the Coding & Algorithms domain, specifically simulation, event processing, and algorithmic data structures.

  • Medium
  • Optiver
  • Coding & Algorithms
  • Software Engineer

Design a balloon stability tracker

Company: Optiver

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: Medium

Interview Round: Take-home Project

Implement a class BalloonFestival to manage hot-air balloons and wind fields over time, and to report rewarded balloons at inspection times. Methods and rules: Initialization - init(yourBalloonNames: list[str]) -> None - Registers the unique names of balloons that belong to your team. Any balloon name not in this list but seen in later events is a competitor. - Assumptions: total number of method calls Q satisfies 1 ≤ Q < 2^20. Operations - BalloonAscended(timestamp: float, balloonName: str, altitude: float) -> bool - Records that balloonName is airborne at the given altitude. This call is used both for first ascent and for changing altitude while airborne. - On ascent, the balloon’s stability is set to stable by default. - Returns True on success; False for invalid input (unknown altitude bounds), or if balloonName is already airborne when ascending without changing altitude, or timestamp ordering is violated. - BalloonDescended(timestamp: float, balloonName: str) -> bool - Records that balloonName has landed (is on the ground and not airborne). - Resets its stability to the default (stable) for the next ascent. - Returns True on success; False if the balloon is already on the ground or timestamp ordering is violated. - SetWindSpeed(timestamp: float, altitude: float, windSpeed: float) -> bool - Sets or replaces the base wind speed anchor at the specified WindAltitude = altitude. - Returns True on success; False if inputs violate bounds or timestamp ordering. - InspectBalloons(timestamp: float) -> list[str] - Returns the lexicographically sorted names of your team’s balloons that are airborne and currently stable, and whose current altitude is greater than or equal to the maximum altitude among all currently stable competitor balloons. If there are no currently stable competitor balloons at this timestamp, return []. Physics and stability rules - Wind anchors: each SetWindSpeed defines a base wind component at a WindAltitude with base magnitude WindSpeedAtAltitude. - Aggregate wind at an arbitrary altitude h is the sum over all anchors: WindSpeed(h) = Σ over anchors [ s / (1 + ((h - A) / 100)^ 2) ], where each anchor has altitude A and base speed s. - Stability threshold: a balloon becomes immediately unstable whenever the instantaneous WindSpeed at its current altitude exceeds 15 m/s (> 15). It can regain stability only if it remains continuously at altitudes where WindSpeed ≤ 15 m/s for at least 300 seconds (inclusive) since the start of that safe period. - On BalloonAscended, the balloon starts as stable by default, subject to immediate re-evaluation against the wind threshold at that timestamp. - On BalloonDescended, the balloon is considered not airborne and ignored in inspections; stability resets for the next ascent. Constraints and assumptions - Altitude bounds: 0 < altitude < 2^15. - Wind speed bounds: 0 ≤ windSpeed < 2^5. - Timestamps are floats and are nondecreasing across all calls. - Multiple wind anchors can coexist; their contributions add cumulatively. Requirements - Design data structures to support up to Q < 2^20 operations efficiently. Aim to handle each method in near O(log N) time, where N is the number of active balloons or wind anchors. - Precisely track stability state transitions over time for each balloon, including when changing altitude while airborne. - Define edge-case behavior (e.g., ascending an unknown balloon name—treat as competitor; re-ascending without descent; updating an existing wind anchor at the same altitude). - Provide the algorithm, data structures, and time/space complexity for each method.

Quick Answer: This question evaluates event-driven simulation, time-ordered state management, floating-point numerical evaluation of aggregate functions, and data-structure design for tracking dynamic object states and stability thresholds; it falls under the Coding & Algorithms domain, specifically simulation, event processing, and algorithmic data structures.

You are building a tracker for a hot-air balloon festival. Over time, balloons change altitude, wind-field anchors are updated, and inspectors request a list of your team’s rewarded balloons. Implement the system described below. --- ## Entities - **Your team balloons**: initialized with a list of unique names. - **Competitor balloons**: any balloon name not in your team list but appearing in later operations. --- ## Operations (processed in given order) All operations include a `timestamp` (float). Timestamps across *all* calls must be **nondecreasing**. ### 1) Initialization - `init(yourBalloonNames: list[str])` Registers the unique names of balloons that belong to your team. ### 2) `BalloonAscended(timestamp, balloonName, altitude) -> bool` Records that `balloonName` is airborne at `altitude`. - Used both for first ascent and for **changing altitude while already airborne**. - On ascent (including altitude change), the balloon’s stability is reset to **stable by default**, then **immediately re-evaluated** against the wind threshold at that timestamp. - Returns `False` if: - timestamp ordering is violated, or - altitude is out of bounds, or - balloon is already airborne and the new altitude equals its current altitude (no-op ascent). ### 3) `BalloonDescended(timestamp, balloonName) -> bool` Records that `balloonName` has landed. - Balloon becomes not-airborne and ignored in inspections. - Stability resets to default (stable) for the next ascent. - Returns `False` if: - timestamp ordering is violated, or - the balloon is already on the ground. ### 4) `SetWindSpeed(timestamp, altitude, windSpeed) -> bool` Sets or replaces a wind anchor at `altitude` with base magnitude `windSpeed`. - Multiple different altitudes may have anchors; contributions add cumulatively. - Replacing an anchor at the same altitude overwrites its base speed. - Returns `False` if: - timestamp ordering is violated, or - altitude or windSpeed is out of bounds. ### 5) `InspectBalloons(timestamp) -> list[str]` Returns the **lexicographically sorted** names of your team’s balloons that are: - airborne, and - currently stable, and - at altitude **>=** the **maximum altitude among all currently stable competitor balloons**. If there are **no currently stable competitor balloons** at that timestamp, return `[]`. If timestamp ordering is violated, return `[]`. --- ## Wind physics Let each wind anchor have altitude `A` and base speed `s`. Aggregate wind speed at altitude `h`: `WindSpeed(h) = Σ [ s / (1 + ((h - A) / 100)^2 ) ]` --- ## Stability rules - A balloon becomes **immediately unstable** whenever `WindSpeed(currentAltitude) > 15`. - It can regain stability only if it remains **continuously** at altitudes where `WindSpeed <= 15` for at least **300 seconds (inclusive)** since the start of that safe period. - On `BalloonAscended`, stability resets to stable by default, then is immediately re-evaluated. - On `BalloonDescended`, balloon is not airborne and stability resets for the next ascent. --- ## Task Write a function `solution(yourBalloonNames, operations)` that simulates all operations and returns a list of results for each operation that produces output: - for Ascend/Descend/Wind: append the returned `bool` - for Inspect: append the returned `list[str]` Each operation is represented as a tuple: - `( "BalloonAscended", timestamp, balloonName, altitude )` - `( "BalloonDescended", timestamp, balloonName )` - `( "SetWindSpeed", timestamp, altitude, windSpeed )` - `( "InspectBalloons", timestamp )`

Constraints

  • 1 ≤ Q = len(operations) < 2^20
  • 0 < altitude < 2^15 (for both balloon altitude and wind-anchor altitude)
  • 0 ≤ windSpeed < 2^5
  • timestamps are floats and must be nondecreasing across all calls
  • yourBalloonNames contains unique strings

Examples

Input: (["A","B"], [("SetWindSpeed",0.0,1000.0,10.0),("BalloonAscended",0.0,"A",1000.0),("BalloonAscended",0.0,"X",1000.0),("InspectBalloons",0.0),("SetWindSpeed",10.0,1000.0,20.0),("InspectBalloons",10.0),("SetWindSpeed",20.0,1000.0,10.0),("InspectBalloons",319.0),("InspectBalloons",320.0)])

Expected Output: [True, True, True, ["A"], True, [], True, [], ["A"]]

Explanation: At t=0 both A (team) and X (competitor) are stable at altitude 1000, so A is rewarded. At t=10 wind becomes unsafe (>15), making both unstable, so no stable competitors => []. After wind becomes safe again at t=20, stability is regained only after 300s of continuous safe wind, so at t=319 still unstable; at t=320 both regain stability and A is rewarded again.

Input: (["T"], [("SetWindSpeed",0.0,1000.0,16.0),("BalloonAscended",0.0,"T",1000.0),("SetWindSpeed",1.0,1000.0,0.0),("InspectBalloons",301.0),("BalloonAscended",301.0,"C",1000.0),("InspectBalloons",301.0),("BalloonAscended",302.0,"C",1200.0),("InspectBalloons",302.0)])

Expected Output: [True, True, True, [], True, ["T"], True, []]

Explanation: T starts unstable (wind 16). Wind becomes safe at t=1, so T regains stability at t=301, but with no stable competitors inspection returns []. After competitor C ascends stable at t=301, T is rewarded. When C moves to altitude 1200, competitor max altitude becomes 1200 so T (at 1000) is no longer rewarded.

Input: (["A"], [("BalloonAscended",1.0,"A",100.0),("BalloonDescended",0.5,"A"),("InspectBalloons",1.0),("BalloonAscended",2.0,"A",100.0),("BalloonAscended",3.0,"A",200.0),("BalloonDescended",4.0,"A"),("BalloonDescended",4.0,"A")])

Expected Output: [True, False, [], False, True, True, False]

Explanation: Descending at timestamp 0.5 violates nondecreasing timestamps (last was 1.0) => False. Inspect has no stable competitors => []. Re-ascending at the same altitude while airborne is invalid => False. Changing altitude while airborne is allowed => True. Descending twice: the second descent fails because already on the ground.

Input: (["AA","AB"], [("SetWindSpeed",0.0,500.0,30.0),("BalloonAscended",0.0,"AA",500.0),("BalloonAscended",0.0,"BB",400.0),("InspectBalloons",0.0),("SetWindSpeed",10.0,500.0,0.0),("InspectBalloons",309.0),("BalloonAscended",310.0,"AB",600.0),("InspectBalloons",310.0)])

Expected Output: [True, True, True, [], True, [], True, ["AA", "AB"]]

Explanation: At t=0, AA is unstable (wind 30 at 500), but competitor BB at 400 is stable because wind there is exactly 15 (safe). No team balloon qualifies => []. Wind becomes 0 at t=10, AA starts its safe period and regains stability at t=310. AB ascends at t=310 stable; competitor BB is stable, max competitor altitude is 400, so both AA (500) and AB (600) are rewarded.

Solution

def solution(yourBalloonNames, operations):
    import heapq

    ALT_MAX = 2 ** 15
    WIND_MAX = 2 ** 5
    THRESH = 15.0
    RECOVER = 300.0
    EPS = 1e-9

    team_set = set(yourBalloonNames)

    class State:
        __slots__ = ("is_team", "airborne", "altitude", "stable", "safe_start", "rec_ver", "comp_ver")
        def __init__(self, is_team):
            self.is_team = is_team
            self.airborne = False
            self.altitude = None
            self.stable = True
            self.safe_start = None
            self.rec_ver = 0       # increments when (re)starting a safe period
            self.comp_ver = 0      # increments when competitor's (altitude/airborne/stable) changes

    # Wind anchors (altitude -> base speed)
    anchors = {}
    wind_version = 0
    wind_cache = {}  # altitude -> (wind_version, computed_value)

    def wind_at(h):
        nonlocal wind_version
        key = h
        cached = wind_cache.get(key)
        if cached is not None and cached[0] == wind_version:
            return cached[1]
        total = 0.0
        for A, s in anchors.items():
            d = (h - A) / 100.0
            total += s / (1.0 + d * d)
        wind_cache[key] = (wind_version, total)
        return total

    balloons = {}  # name -> State

    # altitude -> set(names) for airborne balloons
    alt_to_balloons = {}

    # Heap of recovery deadlines for unstable balloons currently in a safe period
    # items: (due_time, name, rec_ver)
    recovery_heap = []

    # Max heap for stable competitor balloons: (-altitude, name, comp_ver)
    comp_heap = []

    def comp_invalidate_and_maybe_push(name, st):
        """Call after any change that could affect competitor max queries."""
        if st.is_team:
            return
        st.comp_ver += 1
        if st.airborne and st.stable:
            heapq.heappush(comp_heap, (-st.altitude, name, st.comp_ver))

    def get_comp_max_altitude():
        while comp_heap:
            neg_alt, name, ver = comp_heap[0]
            st = balloons.get(name)
            if (st is None or st.is_team or (not st.airborne) or (not st.stable) or st.comp_ver != ver or st.altitude != -neg_alt):
                heapq.heappop(comp_heap)
                continue
            return -neg_alt
        return None

    def start_safe_period_if_needed(name, st, now):
        if st.stable:
            return
        if st.safe_start is None:
            st.safe_start = now
            st.rec_ver += 1
            heapq.heappush(recovery_heap, (now + RECOVER, name, st.rec_ver))

    def clear_safe_period(st):
        st.safe_start = None

    def advance_time(now):
        # Promote balloons that have been continuously safe for >= 300 seconds.
        while recovery_heap and recovery_heap[0][0] <= now + EPS:
            due, name, ver = heapq.heappop(recovery_heap)
            st = balloons.get(name)
            if st is None or (not st.airborne) or st.stable:
                continue
            if st.safe_start is None or st.rec_ver != ver:
                continue
            # Must still be safe at current time.
            if wind_at(st.altitude) <= THRESH + EPS:
                st.stable = True
                st.safe_start = None
                comp_invalidate_and_maybe_push(name, st)
            else:
                # Should be rare (wind changes are processed as operations), but keep consistent.
                st.safe_start = None

    last_ts = float("-inf")

    def process_timestamp(ts):
        nonlocal last_ts
        if ts < last_ts - EPS:
            return False
        advance_time(ts)
        last_ts = ts
        return True

    def valid_alt(a):
        return (a is not None) and (a > 0) and (a < ALT_MAX)

    def valid_wind(w):
        return (w is not None) and (w >= 0) and (w < WIND_MAX)

    out = []

    for op in operations:
        kind = op[0]

        if kind == "BalloonAscended":
            _, ts, name, alt = op
            if not process_timestamp(ts) or (not valid_alt(alt)):
                out.append(False)
                continue

            st = balloons.get(name)
            if st is None:
                st = State(name in team_set)
                balloons[name] = st

            if st.airborne and abs(st.altitude - alt) <= EPS:
                out.append(False)
                continue

            # Remove from old altitude bucket if needed
            if st.airborne:
                old = st.altitude
                s = alt_to_balloons.get(old)
                if s is not None:
                    s.discard(name)
                    if not s:
                        alt_to_balloons.pop(old, None)

            # Apply ascent/altitude-change rules
            st.airborne = True
            st.altitude = alt
            st.stable = True
            st.safe_start = None
            st.rec_ver += 1  # invalidate any pending recovery entries

            alt_to_balloons.setdefault(alt, set()).add(name)

            # Immediate stability evaluation
            if wind_at(alt) > THRESH + EPS:
                st.stable = False
                st.safe_start = None
                comp_invalidate_and_maybe_push(name, st)  # invalidates competitor entries if any
            else:
                comp_invalidate_and_maybe_push(name, st)  # pushes if competitor

            out.append(True)

        elif kind == "BalloonDescended":
            _, ts, name = op
            if not process_timestamp(ts):
                out.append(False)
                continue

            st = balloons.get(name)
            if st is None or (not st.airborne):
                out.append(False)
                continue

            # Remove from altitude bucket
            s = alt_to_balloons.get(st.altitude)
            if s is not None:
                s.discard(name)
                if not s:
                    alt_to_balloons.pop(st.altitude, None)

            st.airborne = False
            st.altitude = None
            st.stable = True
            st.safe_start = None
            st.rec_ver += 1
            comp_invalidate_and_maybe_push(name, st)  # invalidate competitor heap entries

            out.append(True)

        elif kind == "SetWindSpeed":
            _, ts, alt, w = op
            if not process_timestamp(ts) or (not valid_alt(alt)) or (not valid_wind(w)):
                out.append(False)
                continue

            anchors[alt] = float(w)
            wind_version += 1
            wind_cache.clear()

            # Wind changed: re-evaluate all airborne balloons (grouped by altitude)
            # (stability can flip to unstable immediately)
            for a, names in list(alt_to_balloons.items()):
                ws = wind_at(a)
                safe = ws <= THRESH + EPS
                for nm in list(names):
                    st = balloons[nm]
                    if st.stable:
                        if not safe:
                            st.stable = False
                            clear_safe_period(st)
                            comp_invalidate_and_maybe_push(nm, st)
                    else:
                        if safe:
                            start_safe_period_if_needed(nm, st, ts)
                        else:
                            clear_safe_period(st)

            out.append(True)

        elif kind == "InspectBalloons":
            _, ts = op
            if not process_timestamp(ts):
                out.append([])
                continue

            comp_max = get_comp_max_altitude()
            if comp_max is None:
                out.append([])
                continue

            res = []
            for nm in yourBalloonNames:
                st = balloons.get(nm)
                if st is not None and st.airborne and st.stable and st.altitude >= comp_max - EPS:
                    res.append(nm)
            res.sort()
            out.append(res)

        else:
            raise ValueError(f"Unknown operation type: {kind}")

    return out

Time complexity: Let A be the number of wind anchors, B be the number of airborne balloons, and U be the number of distinct altitudes among airborne balloons. Wind evaluation at one altitude is O(A). BalloonAscended/Descended are O(A + log B) (log B for heaps). SetWindSpeed is O(U·A + B) in the worst case (re-evaluating all airborne balloons grouped by altitude). InspectBalloons is O(|team| log |team| + log B).. Space complexity: O(A + B) for anchors, balloon states, altitude buckets, and heaps..

Hints

  1. Track each balloon as a small state machine: airborne/ground, stable/unstable, and (if unstable) when its current safe period started.
  2. To avoid scanning all unstable balloons every time, push (safe_start + 300) into a min-heap; when time advances, promote any balloons whose deadline has passed (and are still safe).
Last updated: Mar 29, 2026

Loading coding console...

PracHub

Master your tech interviews with 8,500+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities
  • Student Access

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.

Related Coding Questions

  • Find missing numbers in sequences - Optiver (hard)
  • Design a circular queue data structure - Optiver (medium)
  • Optimize flight and cargo bookings for profit - Optiver (hard)
  • Compare two programs for equivalence - Optiver (Medium)
  • Design a satellite propagation simulator - Optiver (Medium)