diff --git a/tokio-console/src/input.rs b/tokio-console/src/input.rs
index 47cba9b7b..ce4e3a320 100644
--- a/tokio-console/src/input.rs
+++ b/tokio-console/src/input.rs
@@ -66,6 +66,90 @@ pub(crate) fn is_esc(event: &Event) -> bool {
     )
 }
 
+#[derive(Debug, Clone)]
+pub(crate) enum Event {
+    Key(KeyEvent),
+    Mouse(MouseEvent),
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct KeyEvent {
+    pub code: KeyCode,
+    pub modifiers: KeyModifiers,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) enum KeyCode {
+    Char(char),
+    Enter,
+    Esc,
+    Backspace,
+    Left,
+    Right,
+    Up,
+    Down,
+    Home,
+    End,
+    PageUp,
+    PageDown,
+    Tab,
+    BackTab,
+    Delete,
+    Insert,
+    F(u8),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct KeyModifiers {
+    pub shift: bool,
+    pub control: bool,
+    pub alt: bool,
+    pub super_: bool,
+}
+
+impl Default for KeyModifiers {
+    fn default() -> Self {
+        Self {
+            shift: false,
+            control: false,
+            alt: false,
+            super_: false,
+        }
+    }
+}
+
+pub(crate) fn poll(dur: Duration) -> std::io::Result<Option<Event>> {
+    if crossterm::event::poll(dur)? {
+        let event = crossterm::event::read()?;
+        Ok(Some(convert_event(event)))
+    } else {
+        Ok(None)
+    }
+}
+
+fn convert_event(event: Event) -> Event {
+    match event {
+        Event::Key(key) => Event::Key(KeyEvent {
+            code: convert_key_code(key.code),
+            modifiers: KeyModifiers {
+                shift: key.modifiers.contains(KeyModifiers::shift),
+                control: key.modifiers.contains(KeyModifiers::control),
+                alt: key.modifiers.contains(KeyModifiers::alt),
+                super_: key.modifiers.contains(KeyModifiers::super_),
+            },
+        }),
+        Event::Mouse(mouse) => Event::Mouse(mouse),
+        _ => Event::Key(KeyEvent {
+            code: KeyCode::Char(' '),
+            modifiers: KeyModifiers::default(),
+        }),
+    }
+}
+
+fn convert_key_code(code: KeyCode) -> KeyCode {
+    code
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/tokio-console/src/state/mod.rs b/tokio-console/src/state/mod.rs
index 4693f8cce..116b070fa 100644
--- a/tokio-console/src/state/mod.rs
+++ b/tokio-console/src/state/mod.rs
@@ -16,9 +16,12 @@ use std::{
     convert::{TryFrom, TryInto},
     fmt,
     rc::Rc,
+    sync::atomic::{AtomicU64, Ordering},
     time::{Duration, SystemTime},
+    vec::Vec,
 };
 use tasks::{Details, Task, TasksState};
+use tokio::sync::watch;
 
 pub mod async_ops;
 pub mod histogram;
@@ -30,7 +33,9 @@ pub(crate) use self::store::Id;
 
 pub(crate) type DetailsRef = Rc<RefCell<Option<Details>>>;
 
-#[derive(Default, Debug)]
+const UPDATE_BUFFER_SIZE: usize = 1000;
+const UPDATE_BATCH_INTERVAL: Duration = Duration::from_millis(16); // ~60fps
+
 pub(crate) struct State {
     metas: HashMap<u64, Metadata>,
     last_updated_at: Option<SystemTime>,
@@ -41,6 +46,10 @@ pub(crate) struct State {
     current_task_details: DetailsRef,
     retain_for: Option<Duration>,
     strings: intern::Strings,
+    last_update: watch::Sender<Option<SystemTime>>,
+    update_buffer: Vec<UpdateEvent>,
+    last_batch_time: SystemTime,
+    update_counter: AtomicU64,
 }
 
 pub(crate) enum Visibility {
@@ -100,7 +109,70 @@ pub(crate) struct Attribute {
     unit: Option<String>,
 }
 
+#[derive(Debug)]
+pub(crate) enum UpdateEvent {
+    TaskUpdate(Task),
+    ResourceUpdate(Resource),
+    AsyncOpUpdate(AsyncOp),
+}
+
 impl State {
+    pub(crate) fn new() -> (Self, watch::Receiver<Option<SystemTime>>) {
+        let (tx, rx) = watch::channel(None);
+        (
+            Self {
+                metas: HashMap::new(),
+                last_updated_at: None,
+                temporality: Temporality::Live,
+                tasks_state: TasksState::default(),
+                resources_state: ResourcesState::default(),
+                async_ops_state: AsyncOpsState::default(),
+                current_task_details: Rc::new(RefCell::new(None)),
+                retain_for: None,
+                strings: intern::Strings::new(),
+                last_update: tx,
+                update_buffer: Vec::with_capacity(UPDATE_BUFFER_SIZE),
+                last_batch_time: SystemTime::now(),
+                update_counter: AtomicU64::new(0),
+            },
+            rx,
+        )
+    }
+
+    pub(crate) fn buffer_update(&mut self, event: UpdateEvent) {
+        self.update_buffer.push(event);
+        self.update_counter.fetch_add(1, Ordering::SeqCst);
+
+        let now = SystemTime::now();
+        if now.duration_since(self.last_batch_time).unwrap_or(Duration::ZERO) >= UPDATE_BATCH_INTERVAL
+            || self.update_buffer.len() >= UPDATE_BUFFER_SIZE
+        {
+            self.flush_updates();
+        }
+    }
+
+    pub(crate) fn flush_updates(&mut self) {
+        if self.update_buffer.is_empty() {
+            return;
+        }
+
+        for event in self.update_buffer.drain(..) {
+            match event {
+                UpdateEvent::TaskUpdate(task) => self.tasks_state.update_task(task),
+                UpdateEvent::ResourceUpdate(resource) => self.resources_state.update_resource(resource),
+                UpdateEvent::AsyncOpUpdate(async_op) => self.async_ops_state.update_async_op(async_op),
+            }
+        }
+
+        let now = SystemTime::now();
+        self.last_batch_time = now;
+        let _ = self.last_update.send(Some(now));
+    }
+
+    pub(crate) fn last_updated_at(&self) -> Option<SystemTime> {
+        *self.last_update.borrow()
+    }
+
     pub(crate) fn with_retain_for(mut self, retain_for: Option<Duration>) -> Self {
         self.retain_for = retain_for;
         self
@@ -114,10 +186,6 @@ impl State {
         self
     }
 
-    pub(crate) fn last_updated_at(&self) -> Option<SystemTime> {
-        self.last_updated_at
-    }
-
     pub(crate) fn update(
         &mut self,
         styles: &view::Styles,
diff --git a/tokio-console/src/state/tasks.rs b/tokio-console/src/state/tasks.rs
index 692b65bcf..1e3b9f859 100644
--- a/tokio-console/src/state/tasks.rs
+++ b/tokio-console/src/state/tasks.rs
@@ -50,6 +50,7 @@ pub(crate) enum SortBy {
     Polls = 8,
     Target = 9,
     Location = 10,
+    LastUpdate = 11,
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
@@ -621,6 +622,8 @@ impl SortBy {
             }
             Self::Location => tasks
                 .sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().location.clone())),
+            Self::LastUpdate => tasks
+                .sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().last_update)),
         }
     }
 }
@@ -646,6 +649,7 @@ impl TryFrom<usize> for SortBy {
             idx if idx == Self::Polls as usize => Ok(Self::Polls),
             idx if idx == Self::Target as usize => Ok(Self::Target),
             idx if idx == Self::Location as usize => Ok(Self::Location),
+            idx if idx == Self::LastUpdate as usize => Ok(Self::LastUpdate),
             _ => Err(()),
         }
     }
diff --git a/tokio-console/src/view/mod.rs b/tokio-console/src/view/mod.rs
index e8fff249e..91c746bf4 100644
--- a/tokio-console/src/view/mod.rs
+++ b/tokio-console/src/view/mod.rs
@@ -116,69 +116,67 @@ impl View {
             return update_kind;
         }
 
-        if matches!(event, key!(Char('t'))) {
-            self.state = TasksList;
-            return update_kind;
-        }
-
-        if matches!(event, key!(Char('r'))) {
-            self.state = ResourcesList;
-            return update_kind;
-        }
-
-        match self.state {
-            TasksList => {
-                // The enter key changes views, so handle here since we can
-                // mutate the currently selected view.
-                match event {
-                    key!(Enter) => {
-                        if let Some(task) = self.tasks_list.selected_item() {
-                            update_kind = UpdateKind::SelectTask(task.borrow().span_id());
-                            self.state = TaskInstance(self::task::TaskView::new(
-                                task,
-                                state.task_details_ref(),
-                            ));
+        match event {
+            input::Event::Mouse(mouse_event) => {
+                match mouse_event.kind {
+                    MouseEventKind::Down(_) => {
+                        match self.state {
+                            TasksList => {
+                                if self.tasks_list.handle_resize(mouse_event.column, &area) {
+                                    return update_kind;
+                                }
+                            }
+                            ResourcesList => {
+                                if self.resources_list.handle_resize(mouse_event.column, &area) {
+                                    return update_kind;
+                                }
+                            }
+                            _ => {}
                         }
                     }
-                    _ => {
-                        // otherwise pass on to view
-                        self.tasks_list.update_input(event);
-                    }
-                }
-            }
-            ResourcesList => {
-                match event {
-                    key!(Enter) => {
-                        if let Some(res) = self.resources_list.selected_item() {
-                            update_kind = UpdateKind::SelectResource(res.borrow().span_id());
-                            self.state = ResourceInstance(self::resource::ResourceView::new(res));
+                    MouseEventKind::Up(_) => {
+                        match self.state {
+                            TasksList => self.tasks_list.end_resize(),
+                            ResourcesList => self.resources_list.end_resize(),
+                            _ => {}
                         }
                     }
-                    _ => {
-                        // otherwise pass on to view
-                        self.resources_list.update_input(event);
+                    MouseEventKind::Moved => {
+                        match self.state {
+                            TasksList => {
+                                if self.tasks_list.handle_resize(mouse_event.column, &area) {
+                                    return update_kind;
+                                }
+                            }
+                            ResourcesList => {
+                                if self.resources_list.handle_resize(mouse_event.column, &area) {
+                                    return update_kind;
+                                }
+                            }
+                            _ => {}
+                        }
                     }
+                    _ => {}
                 }
             }
-            ResourceInstance(ref mut view) => {
-                // The escape key changes views, so handle here since we can
-                // mutate the currently selected view.
-                match event {
-                    key!(Esc) => {
-                        self.state = ResourcesList;
-                        update_kind = UpdateKind::Other;
-                    }
-                    key!(Enter) => {
-                        if let Some(op) = view.async_ops_table.selected_item() {
-                            if let Some(task_id) = op.borrow().task_id() {
-                                let task = self
-                                    .tasks_list
-                                    .sorted_items
-                                    .iter()
-                                    .filter_map(|i| i.upgrade())
-                                    .find(|t| task_id == t.borrow().id());
+            input::Event::Key(key_event) => {
+                if matches!(key_event, key!(Char('t'))) {
+                    self.state = TasksList;
+                    return update_kind;
+                }
+
+                if matches!(key_event, key!(Char('r'))) {
+                    self.state = ResourcesList;
+                    return update_kind;
+                }
 
-                                if let Some(task) = task {
+                match self.state {
+                    TasksList => {
+                        // The enter key changes views, so handle here since we can
+                        // mutate the currently selected view.
+                        match key_event {
+                            key!(Enter) => {
+                                if let Some(task) = self.tasks_list.selected_item() {
                                     update_kind = UpdateKind::SelectTask(task.borrow().span_id());
                                     self.state = TaskInstance(self::task::TaskView::new(
                                         task,
@@ -186,28 +184,77 @@ impl View {
                                     ));
                                 }
                             }
+                            _ => {
+                                // otherwise pass on to view
+                                self.tasks_list.update_input(key_event);
+                            }
                         }
                     }
-                    _ => {
-                        // otherwise pass on to view
-                        view.update_input(event);
+                    ResourcesList => {
+                        match key_event {
+                            key!(Enter) => {
+                                if let Some(res) = self.resources_list.selected_item() {
+                                    update_kind = UpdateKind::SelectResource(res.borrow().span_id());
+                                    self.state = ResourceInstance(self::resource::ResourceView::new(res));
+                                }
+                            }
+                            _ => {
+                                // otherwise pass on to view
+                                self.resources_list.update_input(key_event);
+                            }
+                        }
                     }
-                }
-            }
-            TaskInstance(ref mut view) => {
-                // The escape key changes views, so handle here since we can
-                // mutate the currently selected view.
-                match event {
-                    key!(Esc) => {
-                        self.state = TasksList;
-                        update_kind = UpdateKind::ExitTaskView;
+                    ResourceInstance(ref mut view) => {
+                        // The escape key changes views, so handle here since we can
+                        // mutate the currently selected view.
+                        match key_event {
+                            key!(Esc) => {
+                                self.state = ResourcesList;
+                                update_kind = UpdateKind::Other;
+                            }
+                            key!(Enter) => {
+                                if let Some(op) = view.async_ops_table.selected_item() {
+                                    if let Some(task_id) = op.borrow().task_id() {
+                                        let task = self
+                                            .tasks_list
+                                            .sorted_items
+                                            .iter()
+                                            .filter_map(|i| i.upgrade())
+                                            .find(|t| task_id == t.borrow().id());
+
+                                        if let Some(task) = task {
+                                            update_kind = UpdateKind::SelectTask(task.borrow().span_id());
+                                            self.state = TaskInstance(self::task::TaskView::new(
+                                                task,
+                                                state.task_details_ref(),
+                                            ));
+                                        }
+                                    }
+                                }
+                            }
+                            _ => {
+                                // otherwise pass on to view
+                                view.update_input(key_event);
+                            }
+                        }
                     }
-                    _ => {
-                        // otherwise pass on to view
-                        view.update_input(event);
+                    TaskInstance(ref mut view) => {
+                        // The escape key changes views, so handle here since we can
+                        // mutate the currently selected view.
+                        match key_event {
+                            key!(Esc) => {
+                                self.state = TasksList;
+                                update_kind = UpdateKind::ExitTaskView;
+                            }
+                            _ => {
+                                // otherwise pass on to view
+                                view.update_input(key_event);
+                            }
+                        }
                     }
                 }
             }
+            _ => {}
         }
         update_kind
     }
diff --git a/tokio-console/src/view/table.rs b/tokio-console/src/view/table.rs
index c3d03959b..23f04345f 100644
--- a/tokio-console/src/view/table.rs
+++ b/tokio-console/src/view/table.rs
@@ -38,16 +38,92 @@ pub(crate) trait SortBy {
     fn as_column(&self) -> usize;
 }
 
+pub(crate) struct ColumnResizeState {
+    pub resizing: bool,
+    pub column_index: usize,
+    pub initial_width: u16,
+    pub initial_x: u16,
+}
+
+impl Default for ColumnResizeState {
+    fn default() -> Self {
+        Self {
+            resizing: false,
+            column_index: 0,
+            initial_width: 0,
+            initial_x: 0,
+        }
+    }
+}
+
 pub(crate) struct TableListState<T: TableList<N>, const N: usize> {
     pub(crate) sorted_items: Vec<Weak<RefCell<T::Row>>>,
     pub(crate) sort_by: T::Sort,
     pub(crate) selected_column: usize,
     pub(crate) sort_descending: bool,
     pub(crate) table_state: TableState,
-
+    pub(crate) resize_state: ColumnResizeState,
+    pub(crate) column_widths: Vec<u16>,
     last_key_event: Option<input::KeyEvent>,
 }
 
+impl<T, const N: usize> Default for TableListState<T, N>
+where
+    T: TableList<N>,
+    T::Sort: Default,
+{
+    fn default() -> Self {
+        let sort_by = T::Sort::default();
+        let selected_column = sort_by.as_column();
+        Self {
+            sorted_items: Default::default(),
+            sort_by,
+            table_state: Default::default(),
+            selected_column,
+            sort_descending: false,
+            last_key_event: None,
+            resize_state: Default::default(),
+            column_widths: vec![0; N],
+        }
+    }
+}
+
+impl<T, const N: usize> TableListState<T, N>
+where
+    T: TableList<N>,
+{
+    pub(crate) fn handle_resize(&mut self, x: u16, area: &layout::Rect) -> bool {
+        if self.resize_state.resizing {
+            let delta = x.saturating_sub(self.resize_state.initial_x) as i32;
+            let new_width = (self.resize_state.initial_width as i32 + delta).max(5) as u16;
+            self.column_widths[self.resize_state.column_index] = new_width;
+            true
+        } else {
+            // Check if mouse is over column border
+            let mut current_x = area.x;
+            for (idx, &width) in self.column_widths.iter().enumerate() {
+                if x == current_x + width {
+                    self.resize_state.resizing = true;
+                    self.resize_state.column_index = idx;
+                    self.resize_state.initial_width = width;
+                    self.resize_state.initial_x = x;
+                    return true;
+                }
+                current_x += width;
+            }
+            false
+        }
+    }
+
+    pub(crate) fn end_resize(&mut self) {
+        self.resize_state.resizing = false;
+    }
+
+    pub(crate) fn get_column_width(&self, idx: usize) -> u16 {
+        self.column_widths.get(idx).copied().unwrap_or(10)
+    }
+}
+
 impl<T: TableList<N>, const N: usize> TableListState<T, N> {
     pub(in crate::view) fn len(&self) -> usize {
         self.sorted_items.len()
@@ -184,25 +260,6 @@ impl<T: TableList<N>, const N: usize> TableListState<T, N> {
     }
 }
 
-impl<T, const N: usize> Default for TableListState<T, N>
-where
-    T: TableList<N>,
-    T::Sort: Default,
-{
-    fn default() -> Self {
-        let sort_by = T::Sort::default();
-        let selected_column = sort_by.as_column();
-        Self {
-            sorted_items: Default::default(),
-            sort_by,
-            table_state: Default::default(),
-            selected_column,
-            sort_descending: false,
-            last_key_event: None,
-        }
-    }
-}
-
 impl<T, const N: usize> HelpText for TableListState<T, N>
 where
     T: TableList<N>,
diff --git a/tokio-console/src/view/tasks.rs b/tokio-console/src/view/tasks.rs
index 15e8f252c..ce1354f3b 100644
--- a/tokio-console/src/view/tasks.rs
+++ b/tokio-console/src/view/tasks.rs
@@ -20,17 +20,17 @@ use ratatui::{
 #[derive(Debug, Default)]
 pub(crate) struct TasksTable {}
 
-impl TableList<12> for TasksTable {
+impl TableList<13> for TasksTable {
     type Row = Task;
     type Sort = SortBy;
     type Context = ();
 
-    const HEADER: &'static [&'static str; 12] = &[
+    const HEADER: &'static [&'static str; 13] = &[
         "Warn", "ID", "State", "Name", "Total", "Busy", "Sched", "Idle", "Polls", "Kind",
-        "Location", "Fields",
+        "Location", "Fields", "Last Update",
     ];
 
-    const WIDTHS: &'static [usize; 12] = &[
+    const WIDTHS: &'static [usize; 13] = &[
         Self::HEADER[0].len() + 1,
         Self::HEADER[1].len() + 1,
         Self::HEADER[2].len() + 1,
@@ -43,10 +43,11 @@ impl TableList<12> for TasksTable {
         Self::HEADER[9].len() + 1,
         Self::HEADER[10].len() + 1,
         Self::HEADER[11].len() + 1,
+        Self::HEADER[12].len() + 1,
     ];
 
     fn render(
-        table_list_state: &mut TableListState<Self, 12>,
+        table_list_state: &mut TableListState<Self, 13>,
         styles: &view::Styles,
         frame: &mut ratatui::terminal::Frame,
         area: layout::Rect,
@@ -260,6 +261,7 @@ impl TableList<12> for TasksTable {
             kind_width.constraint(),
             location_width.constraint(),
             fields_width,
+            layout::Constraint::Length(15), // Width for Last Update column
         ];
 
         let table = table