Why Last-modified when you already have Expires and Cache-control headers?

(These are rough notes. Do not take it as complete article.)
Last-modified header, as the name suggest, is useful when you want browser to ask the resource only if the contents have changes since the time provided in Last-Modified header. Similar results can be achieved using Expires and cache-control headers with a plus that if you set expire time, browser will not ask the server until the expiration of the terms specified in the headers. This will save a request that results in 304 Not Modified response, but there is a practicle case where using last-modified header along with cache-control or expires headers provide better performance.

When you refresh a page (using F5, or browsers' refresh button) the browser sends a request to the server no matter whether you have told it to keep the page in cache (using Expires or cache-control headers). The same happens to the inline elements (images, style sheets, scripts etc). So, refreshing the page by pressing F5 or browser refresh button could bypass the normal caching techniques. Now if you sent last-modified header with your response last time, the browser will send if-modified-since header along with the request. This can help you save bandwidth by determining whether the contents are modified. You can easily return 304 Not Modified if you think it appropriate. This way you can still make your web application perform better and save your bandwidth even when other caching techniques fail.