[System Design / LLD] Real-world Problem: IoT AC Compressor Logic (Moving Average Stream)

Hey everyone!

I was scrolling through an Instagram reel showing how an air conditioner works under the hood, and my DSA brain immediately kicked in. I realized that the logic an AC uses to sample temperature and toggle its compressor is literally a Sliding Window / Queue problem!

While we already have LeetCode 346: Moving Average from Data Stream (which is a premium question), this real-world adaptation introduces physical hardware constraints like Hysteresis (Deadband) to protect the compressor from short-cycling. I thought it would make an amazing interview question for both DSA and Low-Level Design (LLD).

Below is the LeetCode-style problem framework, followed by a LLD simulation in Go using a concurrent ticker.


The LeetCode Style Prompt

Problem Title: Design an AC Compressor Controller

Difficulty: Medium

Tags: Design, Queue, Sliding Window, Data Stream

Description:

Design an air conditioner controller system that decides whether to turn a cooling compressor ON or OFF based on the moving average of the last temperature readings.

To prevent the compressor from rapidly flipping on and off (which damages the physical hardware motor), the system uses a hysteresis threshold of around the targetTemp.

Implement the ACCmpController class:

  • ACCmpController(int size, double targetTemp) Initializes the object with the window size for the moving average and the desired targetTemp.
  • string addReading(double temp) Adds a new temperature reading to the stream.
  • If the system has fewer than size readings, it should return "WAITING".
  • Once readings are present, it calculates the moving average of the last readings:
  • If , return "ON".
  • If , return "OFF".
  • Otherwise, return "NO_CHANGE".

Example:

Input:
["ACCmpController", "addReading", "addReading", "addReading", "addReading"]
[[3, 24.0], [26.0], [25.5], [25.0], [22.0]]

Output:
[null, "WAITING", "WAITING", "ON", "OFF"]

LLD Implementation (Not LeetCode Style)

Instead of the standard LeetCode class template, here is a real-world Low-Level Design (LLD). In a real smart device, temperatures arrive asynchronously via a sensor. This implementation uses a concurrent Ticker in Go to poll a sensor at regular intervals, calculates the average thread-safely using a circular queue, and triggers the hardware compressor state.

package main

import (
	"context"
	"fmt"
	"math/rand"
	"sync"
	"time"
)

// 1. DOMAIN COMPONENT: The Hardware Layer (Interfaces)

type TemperatureSensor interface {
	ReadCurrentTemp() float64
}

type Compressor interface {
	TurnOn()
	TurnOff()
	GetState() string
}


// 2. CORE DATA STRUCTURE: Thread-Safe Circular Queue (Moving Average Window)

type MovingAverageWindow struct {
	mu       sync.Mutex
	readings []float64
	size     int
	head     int
	tail     int
	count    int
	sum      float64
}

func NewMovingAverageWindow(size int) *MovingAverageWindow {
	return &MovingAverageWindow{
		readings: make([]float64, size),
		size:     size,
	}
}

func (m *MovingAverageWindow) AddAndCalculate(val float64) (float64, bool) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if m.count == m.size {
		m.sum -= m.readings[m.head]
		m.head = (m.head + 1) % m.size
		m.count--
	}

	m.readings[m.tail] = val
	m.sum += val
	m.tail = (m.tail + 1) % m.size
	m.count++

	// Only return a valid average once the sliding window is fully primed
	if m.count < m.size {
		return 0.0, false
	}
	return m.sum / float64(m.size), true
}


// 3. SYSTEM CONTROLLER: Manages Ticker, Sensor Data, and Logic
type SmartACController struct {
	sensor     TemperatureSensor
	compressor Compressor
	window     *MovingAverageWindow
	targetTemp float64
}

func NewSmartACController(sensor TemperatureSensor, comp Compressor, windowSize int, target float64) *SmartACController {
	return &SmartACController{
		sensor:     sensor,
		compressor: comp,
		window:     NewMovingAverageWindow(windowSize),
		targetTemp: target,
	}
}

// StartMonitoring spawns a background routine driven by a ticker
func (ac *SmartACController) StartMonitoring(ctx context.Context, interval time.Duration) {
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	fmt.Printf("[System] Monitoring started. Target Temp: %.1f°C\n", ac.targetTemp)
	fmt.Println("------------------------------------------------------------------")

	for {
		select {
		case <-ctx.Done():
			fmt.Println("[System] Monitoring stopped.")
			return
		case <-ticker.C:
			// 1. Read temperature from the hardware sensor
			currTemp := ac.sensor.ReadCurrentTemp()

			// 2. Feed it into our moving average window
			avg, initialized := ac.window.AddAndCalculate(currTemp)
			if !initialized {
				fmt.Printf("[Sensor] Reading: %.2f°C | Gathering initial readings...\n", currTemp)
				continue
			}

			// 3. Evaluate Hysteresis Logic to toggle Compressor state
			ac.evaluateState(currTemp, avg)
		}
	}
}

func (ac *SmartACController) evaluateState(curr float64, avg float64) {
	fmt.Printf("[Sensor] Reading: %.2f°C | Running Avg: %.2f°C | Comp State: %s\n", 
		curr, avg, ac.compressor.GetState())

	// Hysteresis bound checks
	if avg > ac.targetTemp+0.5 {
		ac.compressor.TurnOn()
	} else if avg < ac.targetTemp-0.5 {
		ac.compressor.TurnOff()
	}
}


// 4. MOCK HARDWARE IMPLEMENTATIONS FOR SIMULATION


type MockSensor struct {
	currentTemp float64
}

func (m *MockSensor) ReadCurrentTemp() float64 {
	// Simulate minor fluctuations in the room temperature
	m.currentTemp += (rand.Float64() - 0.48) // Drifts slowly up or down
	return m.currentTemp
}

type MockCompressor struct {
	isOn bool
}

func (m *MockCompressor) TurnOn() {
	if !m.isOn {
		fmt.Println(" >>> [HARDWARE UPDATE] Room too hot. Compressor kicked ON.")
		m.isOn = true
	}
}

func (m *MockCompressor) TurnOff() {
	if m.isOn {
		fmt.Println(" >>> [HARDWARE UPDATE] Room cooled down. Compressor kicked OFF.")
		m.isOn = false
	}
}

func (m *MockCompressor) GetState() string {
	if m.isOn {
		return "ON"
	}
	return "OFF"
}


// 5. RUNNING THE LLD SIMULATION


func main() {
	// Initialize hardware defaults
	sensor := &MockSensor{currentTemp: 26.5} // Room starts hot at 26.5°C
	compressor := &MockCompressor{isOn: false}

	// Create Controller: Window size 5, Target 24.0°C
	acSystem := NewSmartACController(sensor, compressor, 5, 24.0)

	// Context to limit the life of our simulation run
	ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
	defer cancel()

	// Ticker triggers a sensor reading every 1 second
	acSystem.StartMonitoring(ctx, 1*time.Second)
}

What do you think?

Would you classify this as an Easy or a Medium?

Also I think we should feature more of these common, daily-use real-world problems on platforms like LeetCode. There are so many things we interact with in our everyday lives that we don’t even think twice about, yet they can be beautifully translated into code.

Comments (0)