Charm Bracelet
Examples

Debounce

What This Example Shows

The debounce example shows you how to handle situations where users might be hammering keys really fast, but you only want to do something after they stop typing for a bit. Think of it like waiting for someone to finish talking before you respond - except here we're waiting for them to stop pressing keys.

This example waits for one full second of silence before exiting the app. It's a great way to prevent accidental actions when users are typing quickly.

Understanding Debouncing

You know how sometimes you type really fast and your computer can't keep up? Or when you're searching for something and it tries to search after every single letter you type? That's annoying. Debouncing fixes this by saying "hey, let's wait until the user takes a break before we actually do anything."

Without debouncing, rapid key presses could trigger unwanted actions or overwhelm your system with too many requests.

This technique is super useful when you're building things like:

  • Search boxes that query as you type
  • Auto-save features
  • Any operation that's expensive to run repeatedly

How It Works

The Counter System

The app uses a simple counter (called tag) that goes up every time you press a key. Think of it like a ticket number at the deli - each key press gets the next number in line.

type model struct {
    tag int  // This number increases with each key press
}

Setting the Timer

When you press a key, two things happen instantly:

  • The counter bumps up by one
  • A one-second timer starts, and we remember what the counter was when we started it

Checking When Time's Up

After one second, the timer sends us a message. We check: "Is the counter number in this message the same as our current counter?"

  • If yes: Nobody pressed any keys during the wait, so we can exit safely
  • If no: Someone pressed another key while waiting, so we ignore this old timer

This approach lets multiple timers run at once, but only the most recent one matters. It's like having several alarms set, but only listening to the latest one.

The Code Structure

The model keeps things simple with just one field:

type model struct {
    tag int  // Tracks the most recent key press
}

This handles all the logic:

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyMsg:
        // Bump the counter and start a timer
        m.tag++
        return m, tea.Tick(debounceDuration, func(_ time.Time) tea.Msg {
            return exitMsg(m.tag)  // Remember this counter value
        })
    case exitMsg:
        // Check if this timer is still relevant
        if int(msg) == m.tag {
            return m, tea.Quit  // Exit if no newer key presses
        }
    }
    return m, nil
}

Shows the current state and instructions:

func (m model) View() string {
    return fmt.Sprintf("Key presses: %d", m.tag) +
        "\nTo exit press any key, then wait for one second without pressing anything."
}
debounce example demonstration

Final Code

main.go
package main

import (
	"fmt"
	"os"
	"time"

	tea "github.com/charmbracelet/bubbletea"
)

const debounceDuration = time.Second

type exitMsg int

type model struct {
	tag int
}

func (m model) Init() tea.Cmd {
	return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		// Increment the tag on the model...
		m.tag++
		return m, tea.Tick(debounceDuration, func(_ time.Time) tea.Msg {
			// ...and include a copy of that tag value in the message.
			return exitMsg(m.tag)
		})
	case exitMsg:
		// If the tag in the message doesn't match the tag on the model then we
		// know that this message was not the last one sent and another is on
		// the way. If that's the case we know, we can ignore this message.
		// Otherwise, the debounce timeout has passed and this message is a
		// valid debounced one.
		if int(msg) == m.tag {
			return m, tea.Quit
		}
	}

	return m, nil
}

func (m model) View() string {
	return fmt.Sprintf("Key presses: %d", m.tag) +
		"\nTo exit press any key, then wait for one second without pressing anything."
}

func main() {
	if _, err := tea.NewProgram(model{}).Run(); err != nil {
		fmt.Println("uh oh:", err)
		os.Exit(1)
	}
}

How is this guide?