diff --git a/embassy-sync/CHANGELOG.md b/embassy-sync/CHANGELOG.md
index e7db97ef7..3f6b39d8b 100644
--- a/embassy-sync/CHANGELOG.md
+++ b/embassy-sync/CHANGELOG.md
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+- Add `len`, `is_empty` and `is_full` functions to `Channel`.
+
 ## 0.5.0 - 2023-12-04
 
 - Add a PriorityChannel.
@@ -35,7 +39,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Remove unnecessary uses of `atomic-polyfill`
 - Add `#[must_use]` to all futures.
 
-
 ## 0.1.0 - 2022-08-26
 
 - First release
diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs
index 48f4dafd6..18be462cb 100644
--- a/embassy-sync/src/channel.rs
+++ b/embassy-sync/src/channel.rs
@@ -449,6 +449,18 @@ impl<T, const N: usize> ChannelState<T, N> {
             Poll::Pending
         }
     }
+
+    fn len(&self) -> usize {
+        self.queue.len()
+    }
+
+    fn is_empty(&self) -> bool {
+        self.queue.is_empty()
+    }
+
+    fn is_full(&self) -> bool {
+        self.queue.is_full()
+    }
 }
 
 /// A bounded channel for communicating between asynchronous tasks
@@ -572,6 +584,21 @@ where
     pub fn try_receive(&self) -> Result<T, TryReceiveError> {
         self.lock(|c| c.try_receive())
     }
+
+    /// Returns the number of elements currently in the channel.
+    pub fn len(&self) -> usize {
+        self.lock(|c| c.len())
+    }
+
+    /// Returns whether the channel is empty.
+    pub fn is_empty(&self) -> bool {
+        self.lock(|c| c.is_empty())
+    }
+
+    /// Returns whether the channel is full.
+    pub fn is_full(&self) -> bool {
+        self.lock(|c| c.is_full())
+    }
 }
 
 /// Implements the DynamicChannel to allow creating types that are unaware of the queue size with the