Hone logo
Hone
Problems

Implement a Custom Ticker in Go

This challenge requires you to implement a simplified version of Go's built-in time.Ticker. You will create a struct that, when a method is called, will repeatedly send a value on a channel at a specified interval. This is a fundamental pattern for scheduling recurring tasks in Go applications.

Problem Description

Your task is to create a Go struct, let's call it CustomTicker, that mimics the behavior of time.Ticker. This CustomTicker should have a method that, when invoked, starts a goroutine to send a signal (an empty struct struct{}) on a provided channel at regular, configurable intervals.

Key Requirements:

  1. NewCustomTicker(interval time.Duration) function: This function should return a pointer to a CustomTicker instance. It will take the desired interval between ticks as an argument.
  2. Start(c chan<- struct{}) method: This method, called on a CustomTicker instance, should:
    • Start a new goroutine.
    • Inside the goroutine, continuously wait for the specified interval and then send an empty struct (struct{}) onto the provided channel c.
    • The goroutine should continue to run as long as the CustomTicker is active.
  3. Stop() method: This method should gracefully stop the ticker's goroutine, preventing further signals from being sent on the channel.
  4. Channel Usage: The CustomTicker should not create its own channel but rather send signals on a channel provided by the caller to the Start method.

Expected Behavior:

When Start is called, the provided channel c will begin receiving signals every interval duration. Calling Stop will halt these signals.

Edge Cases:

  • What happens if Stop is called before Start?
  • What happens if Start is called multiple times on the same CustomTicker instance without calling Stop in between?
  • What happens if the caller stops reading from the channel c? (While not strictly your responsibility to handle as the ticker, it's good to be aware of potential blocking scenarios).

Examples

Example 1:

package main

import (
	"fmt"
	"time"
)

// Assume CustomTicker is implemented as per the problem description.

func main() {
	ticker := NewCustomTicker(500 * time.Millisecond) // Tick every 500ms
	tickChan := make(chan struct{})

	go func() {
		ticker.Start(tickChan)
		fmt.Println("Ticker started.")
	}()

	// Simulate receiving ticks for a short period
	for i := 0; i < 5; i++ {
		<-tickChan // Wait for a tick
		fmt.Printf("Tick received at: %s\n", time.Now().Format("15:04:05.000"))
	}

	ticker.Stop()
	fmt.Println("Ticker stopped.")

	// Give a moment to ensure no more ticks are sent
	time.Sleep(600 * time.Millisecond)
}

Expected Output (timing will vary slightly):

Ticker started.
Tick received at: 10:30:00.500
Tick received at: 10:30:01.000
Tick received at: 10:30:01.500
Tick received at: 10:30:02.000
Tick received at: 10:30:02.500
Ticker stopped.

Explanation: The CustomTicker is initialized to tick every 500 milliseconds. It's started, and the main goroutine then waits for 5 ticks, printing a timestamp for each. After receiving 5 ticks, Stop is called, and the program exits.

Example 2:

package main

import (
	"fmt"
	"time"
)

// Assume CustomTicker is implemented as per the problem description.

func main() {
	ticker := NewCustomTicker(1 * time.Second) // Tick every 1 second
	tickChan := make(chan struct{})

	go func() {
		ticker.Start(tickChan)
		fmt.Println("Ticker started.")
	}()

	// Stop immediately after starting
	time.Sleep(100 * time.Millisecond) // Small delay to ensure goroutine starts
	ticker.Stop()
	fmt.Println("Ticker stopped immediately.")

	// Wait to see if any ticks are sent (they should not be)
	select {
	case <-tickChan:
		fmt.Println("Unexpected tick received!")
	case <-time.After(1 * time.Second):
		fmt.Println("No unexpected ticks received.")
	}
}

Expected Output:

Ticker started.
Ticker stopped immediately.
No unexpected ticks received.

Explanation: The ticker is started, but Stop is called very shortly after. The select statement demonstrates that no ticks are received within the subsequent second, confirming that Stop worked correctly.

Constraints

  • The interval provided to NewCustomTicker will be a positive time.Duration (e.g., time.Second, 100 * time.Millisecond).
  • The chan<- struct{} passed to Start will be a valid, non-nil channel.
  • The implementation should be efficient and avoid busy-waiting.

Notes

  • Consider how to signal the goroutine to terminate gracefully when Stop() is called. Channels are excellent for this in Go.
  • Think about potential race conditions if Start or Stop are called concurrently. For this challenge, assume Start and Stop will not be called concurrently on the same CustomTicker instance.
  • The Stop() method should ideally prevent new ticks from being sent after it's called, but it doesn't need to cancel a tick that is already in progress of being sent (though a robust implementation might aim for this).
Loading editor...
go