PracHub
QuestionsPremiumCoachesLearningGuidesInterview Prep

Quick Overview

This question evaluates proficiency in data structures, event-driven simulation, numerical aggregation of spatially varying fields, and time-based state tracking for mutable entities.

  • Medium
  • Optiver
  • Coding & Algorithms
  • Software Engineer

Design a Balloon Festival Simulator

Company: Optiver

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: Medium

Interview Round: Take-home Project

Implement a class BalloonFestival with the following API and rules. Goal Track hot air balloons (yours and competitors), evolving wind fields, and balloon stability over time. When inspected at a timestamp, return the sorted names of your team’s balloons that are stable and flying at or above the highest stable competitor balloon. Initialization init(yourBalloonNames: list[str]) - Registers the set of balloon names that belong to your team; all other names that appear later are competitors. - Each balloon name is unique. - Constraint: 1 ≤ len(yourBalloonNames) < 2^20. Operations BalloonAscended(timestamp: float, balloonName: str, altitude: float) -> bool - Marks a balloon as ascended to the given altitude and updates its state. - Balloons are stable by default upon ascending. - Returns True if the state was updated successfully; otherwise False. BalloonDescended(timestamp: float, balloonName: str) -> bool - Marks a balloon as descended to the ground and resets its stability to the default. - Returns True on success; otherwise False. SetWindSpeed(timestamp: float, altitude: float, windSpeed: float) -> bool - Updates the wind speed centered at the given altitude. - Constraints: 0 < altitude < 2^15, 0 ≤ windSpeed < 2^5. - Returns True on success; otherwise False. InspectBalloons(timestamp: float) -> list[str] - Returns the sorted names of your team’s balloons that are stable at the given timestamp and whose altitudes are ≥ the highest stable competitor balloon at that timestamp. If none qualify, return []. Stability Rules - A balloon becomes immediately unstable if the wind speed at its altitude exceeds 15 m/s. - An unstable balloon regains stability only after it has remained at an altitude where the wind speed is ≤ 15 m/s for at least 300 seconds (inclusive). Wind Model - Wind from multiple defined altitudes adds cumulatively. - Let WindSpeedAtAltitude be the wind defined at WindAltitude, and h be a balloon’s current altitude. - The contribution from that wind source at altitude h is: WindSpeed(h) = WindSpeedAtAltitude / (1 + ((h - WindAltitude) / 100)^ 2) - The total wind speed at altitude h is the sum of contributions from all defined wind sources at the inspection time. Requirements - Design data structures and algorithms to support the above operations correctly and efficiently over many events.

Quick Answer: This question evaluates proficiency in data structures, event-driven simulation, numerical aggregation of spatially varying fields, and time-based state tracking for mutable entities.

Implement a simulator for a hot air balloon festival. You will write a function that processes a sequence of timestamped operations on a virtual system. Some balloons belong to your team; all other balloon names that appear are competitors. When inspected at a timestamp, the simulator must return the **sorted names** of your team’s balloons that are: 1) **Stable** at that timestamp, and 2) Flying at an altitude **greater than or equal to** the altitude of the **highest stable competitor** balloon at that timestamp. If no competitor balloon is stable, then every stable, airborne balloon of yours qualifies. --- ### Operations The system supports the following operations (given in non-decreasing timestamp order): - `BalloonAscended(timestamp, balloonName, altitude) -> bool` - Marks a balloon as airborne at `altitude`. - On ascent, the balloon is **stable by default**, but it becomes **immediately unstable** if the wind speed at that altitude is > 15 m/s. - Returns `False` if the balloon is already airborne; otherwise `True`. - `BalloonDescended(timestamp, balloonName) -> bool` - Marks a balloon as on the ground (not airborne). - Returns `False` if the balloon is not currently airborne; otherwise `True`. - `SetWindSpeed(timestamp, altitude, windSpeed) -> bool` - Sets/overwrites the wind source centered at `altitude` to have strength `windSpeed`. - Returns `False` if constraints are violated; otherwise `True`. - `InspectBalloons(timestamp) -> list[str]` - Returns the sorted list of your team balloon names that qualify at `timestamp`. If an operation’s timestamp is **less** than the timestamp of the previous processed operation, it is considered invalid and has **no effect**: - It returns `False` for boolean-returning operations. - It returns `[]` for `InspectBalloons`. --- ### Stability rules - A balloon becomes **immediately unstable** if total wind speed at its altitude is **> 15**. - An unstable balloon becomes stable again only after it has continuously remained at an altitude where wind speed is **≤ 15** for **at least 300 seconds (inclusive)**. --- ### Wind model Wind consists of multiple sources. A wind source is defined by `(WindAltitude, WindSpeedAtAltitude)`. The contribution of one source at altitude `h` is: `contrib(h) = WindSpeedAtAltitude / (1 + ((h - WindAltitude) / 100)^2)` The **total wind speed** at altitude `h` is the **sum** of contributions from all currently defined wind sources. `SetWindSpeed` overwrites any existing source at the same `WindAltitude`. --- ### What you must implement Write `solution(yourBalloonNames, operations)`. - `yourBalloonNames` is `list[str]`. - `operations` is a list of tuples. Each tuple begins with the operation name string: - `("BalloonAscended", timestamp, balloonName, altitude)` - `("BalloonDescended", timestamp, balloonName)` - `("SetWindSpeed", timestamp, altitude, windSpeed)` - `("InspectBalloons", timestamp)` Return a list of outputs corresponding to each operation in order (booleans for the first three, lists for `InspectBalloons`).

Constraints

  • 1 ≤ len(yourBalloonNames) < 2^20
  • Operations are intended to be given in non-decreasing timestamp order; if an operation has timestamp < previous processed timestamp, it must be treated as invalid with no effect
  • For this problem, altitudes are integers in meters
  • 0 < altitude < 2^15 (i.e., 1..32767) for both balloons and wind sources
  • 0 ≤ windSpeed < 2^5 (i.e., 0..31)
  • Unstable balloons regain stability after 300 seconds (inclusive) continuously in wind ≤ 15 m/s

Examples

Input: (["Alpha","Bravo"], [("BalloonAscended", 0, "Alpha", 1000), ("BalloonAscended", 0, "Bravo", 500), ("InspectBalloons", 10)])

Expected Output: [True, True, ["Alpha", "Bravo"]]

Explanation: No competitors exist; both balloons are stable and airborne, so both are returned sorted.

Input: (["A","B"], [("BalloonAscended", 0, "A", 1000), ("BalloonAscended", 0, "Rival", 1200), ("InspectBalloons", 1), ("BalloonAscended", 2, "B", 1500), ("InspectBalloons", 3)])

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

Explanation: At t=1 the highest stable competitor is at 1200 so A(1000) doesn't qualify. After B ascends to 1500, B qualifies at t=3.

Input: (["A"], [("BalloonAscended", 0, "Rival", 1200), ("BalloonAscended", 0, "A", 1000), ("InspectBalloons", 1), ("SetWindSpeed", 5, 1200, 20), ("InspectBalloons", 5)])

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

Explanation: Before wind is set, Rival is stable at 1200 so A(1000) doesn't qualify. Setting wind 20 at 1200 makes Rival immediately unstable; then no stable competitor exists and A qualifies.

Input: (["A"], [("SetWindSpeed", 0, 1000, 16), ("BalloonAscended", 10, "A", 1000), ("InspectBalloons", 20), ("SetWindSpeed", 100, 1000, 10), ("InspectBalloons", 399), ("InspectBalloons", 400)])

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

Explanation: A is unstable upon ascent because wind>15 at 1000. Wind becomes safe at t=100; after 300 seconds (at t=400 inclusive), A regains stability and is returned.

Input: (["A"], [("BalloonAscended", 10, "A", 100), ("BalloonDescended", 5, "A"), ("InspectBalloons", 10)])

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

Explanation: The descend operation goes backward in time (5 < 10) so it is invalid and has no effect; A remains airborne and stable at t=10.

Input: (["A"], [("SetWindSpeed", 0, 0, 10), ("SetWindSpeed", 1, 1000, 32), ("BalloonAscended", 2, "A", 1000), ("InspectBalloons", 2)])

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

Explanation: Both wind updates are invalid (altitude=0 and windSpeed=32), so wind stays 0. A ascends stable and is returned.

Input: (["Solo"], [])

Expected Output: []

Explanation: No operations produce no outputs.

Solution

def solution(yourBalloonNames, operations):
    import heapq

    MAX_ALT = 1 << 15  # 32768, valid altitudes: 1..32767
    SAFE_LIMIT = 15.0
    REGAIN_SECONDS = 300
    EPS = 1e-9

    your_set = set(yourBalloonNames)

    # Precompute kernel for distance d in [-32767, 32767]
    # kernel(d) = 1 / (1 + (d/100)^2)
    offset = MAX_ALT - 1
    kernel = [0.0] * (2 * (MAX_ALT - 1) + 1)
    for d in range(-(MAX_ALT - 1), (MAX_ALT - 1) + 1):
        x = d / 100.0
        kernel[d + offset] = 1.0 / (1.0 + x * x)

    # Wind field computed at every integer altitude.
    wind_field = [0.0] * MAX_ALT
    safe_flag = [True] * MAX_ALT  # True iff wind_field[h] <= 15
    wind_sources = {}  # center_altitude -> windSpeedAtAltitude

    class Balloon:
        __slots__ = ("is_yours", "airborne", "alt", "stable", "safe_start", "ver")

        def __init__(self, is_yours):
            self.is_yours = is_yours
            self.airborne = False
            self.alt = 0
            self.stable = True
            self.safe_start = None
            self.ver = 0

    balloons = {}  # name -> Balloon
    alt_to_balloons = {}  # altitude -> set(names) for airborne balloons at that exact altitude

    # Heap of scheduled regain-stability events: (due_time, name, ver_at_schedule)
    regain_heap = []

    # Heap to track highest stable competitor: (-alt, name, ver_at_push)
    comp_heap = []

    current_time = float("-inf")

    def get_balloon(name):
        b = balloons.get(name)
        if b is None:
            b = Balloon(is_yours=(name in your_set))
            balloons[name] = b
        return b

    def remove_from_alt(name, alt):
        s = alt_to_balloons.get(alt)
        if not s:
            return
        s.discard(name)
        if not s:
            alt_to_balloons.pop(alt, None)

    def add_to_alt(name, alt):
        s = alt_to_balloons.get(alt)
        if s is None:
            s = set()
            alt_to_balloons[alt] = s
        s.add(name)

    def mark_unstable(b, ts):
        # Only do work if it changes something relevant
        if b.stable or b.safe_start is not None:
            b.ver += 1
            b.stable = False
            b.safe_start = None

    def start_safe_timer_if_needed(b, ts, name):
        # Called when altitude becomes safe while balloon is unstable.
        if not b.stable and b.safe_start is None:
            b.ver += 1
            b.safe_start = ts
            heapq.heappush(regain_heap, (ts + REGAIN_SECONDS, name, b.ver))

    def advance_time(ts):
        nonlocal current_time
        # Process any stability regains due at/before ts.
        while regain_heap and regain_heap[0][0] <= ts:
            due, name, ver = heapq.heappop(regain_heap)
            b = balloons.get(name)
            if b is None or not b.airborne:
                continue
            if b.ver != ver:
                continue
            if b.stable:
                continue
            if b.alt <= 0 or b.alt >= MAX_ALT:
                continue
            # Must still be in safe wind at the due time.
            if safe_flag[b.alt]:
                b.ver += 1
                b.stable = True
                b.safe_start = None
                if not b.is_yours:
                    heapq.heappush(comp_heap, (-b.alt, name, b.ver))
        current_time = ts

    def current_max_stable_competitor_alt():
        while comp_heap:
            neg_alt, name, ver = comp_heap[0]
            alt = -neg_alt
            b = balloons.get(name)
            if (
                b is not None
                and b.airborne
                and (not b.is_yours)
                and b.stable
                and b.ver == ver
                and b.alt == alt
            ):
                return alt
            heapq.heappop(comp_heap)
        return None

    def set_wind(ts, center_alt, speed):
        # Validate
        if not isinstance(center_alt, int) or not (1 <= center_alt < MAX_ALT):
            return False
        if not (isinstance(speed, int) or isinstance(speed, float)):
            return False
        if speed < 0 or speed >= (1 << 5):
            return False

        old = wind_sources.get(center_alt, 0.0)
        new = float(speed)
        if abs(new - old) < 1e-15:
            # No change
            wind_sources[center_alt] = new
            return True

        wind_sources[center_alt] = new
        delta = new - old

        # Update wind at all altitudes and handle safe/unsafe flips.
        for h in range(1, MAX_ALT):
            wind_field[h] += delta * kernel[h - center_alt + offset]
            new_safe = wind_field[h] <= (SAFE_LIMIT + EPS)
            if new_safe != safe_flag[h]:
                safe_flag[h] = new_safe
                names = alt_to_balloons.get(h)
                if names:
                    # Copy to avoid issues if someone modifies sets elsewhere (we don't here, but safe).
                    for nm in tuple(names):
                        b = balloons.get(nm)
                        if b is None or not b.airborne or b.alt != h:
                            continue
                        if not new_safe:
                            mark_unstable(b, ts)
                        else:
                            start_safe_timer_if_needed(b, ts, nm)
        return True

    out = []

    # Initialize time at 0 if there are ops; otherwise return empty output
    if not operations:
        return out
    current_time = float("-inf")

    for op in operations:
        if not op:
            out.append(False)
            continue

        kind = op[0]
        # Extract timestamp safely
        try:
            ts = op[1]
        except Exception:
            # Malformed operation
            out.append([] if kind == "InspectBalloons" else False)
            continue

        # Invalid timestamp ordering => no effect
        if ts < current_time:
            out.append([] if kind == "InspectBalloons" else False)
            continue

        advance_time(ts)

        if kind == "BalloonAscended":
            if len(op) != 4:
                out.append(False)
                continue
            _, _, name, alt = op
            if not isinstance(alt, int) or not (1 <= alt < MAX_ALT):
                out.append(False)
                continue

            b = get_balloon(name)
            if b.airborne:
                out.append(False)
                continue

            b.ver += 1
            b.airborne = True
            b.alt = alt
            b.safe_start = None

            add_to_alt(name, alt)

            if safe_flag[alt]:
                b.stable = True
                if not b.is_yours:
                    heapq.heappush(comp_heap, (-alt, name, b.ver))
            else:
                b.stable = False
                # safe_start remains None until wind becomes safe

            out.append(True)

        elif kind == "BalloonDescended":
            if len(op) != 3:
                out.append(False)
                continue
            _, _, name = op
            b = balloons.get(name)
            if b is None or not b.airborne:
                out.append(False)
                continue

            remove_from_alt(name, b.alt)
            b.ver += 1
            b.airborne = False
            b.alt = 0
            b.stable = True
            b.safe_start = None
            out.append(True)

        elif kind == "SetWindSpeed":
            if len(op) != 4:
                out.append(False)
                continue
            _, _, center_alt, speed = op
            out.append(set_wind(ts, center_alt, speed))

        elif kind == "InspectBalloons":
            # Ensure due regain events are already processed via advance_time
            max_comp = current_max_stable_competitor_alt()
            threshold = max_comp if max_comp is not None else float("-inf")

            res = []
            for name, b in balloons.items():
                if b.is_yours and b.airborne and b.stable and b.alt >= threshold:
                    res.append(name)
            res.sort()
            out.append(res)

        else:
            # Unknown operation
            out.append([] if kind == "InspectBalloons" else False)

    return out

Time complexity: Let H = 32767 possible altitudes. Each SetWindSpeed runs in O(H + K) where K is the number of airborne balloons at altitudes whose safe/unsafe status flips. Other operations are O(log M) for heap usage plus O(Y) per Inspect to scan your balloons (Y ≤ total balloons).. Space complexity: O(H + B + S) where H=32768 for wind arrays, B is number of balloons seen, and S is number of scheduled regain events in the heap..

Hints

  1. Track when an unstable balloon enters a safe-wind condition and schedule the earliest time it could become stable again (e.g., with a min-heap).
  2. To find the highest stable competitor efficiently, consider a heap with lazy deletion (store versions and discard stale entries).
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)