diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a5cff31d7..758f0f89b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
 * [ENHANCEMENT] Index Cache: Multi level cache adds config `max_backfill_items` to cap max items to backfill per async operation. #5686
 * [ENHANCEMENT] Query Frontend: Log number of split queries in `query stats` log. #5703
 * [BUGFIX] Distributor: Do not use label with empty values for sharding #5717
+* [BUGFIX] Query Frontend: queries with negative offset should check whether it is cacheable or not. #5719
 
 
 ## 1.16.0 2023-11-20
diff --git a/pkg/querier/tripperware/queryrange/results_cache.go b/pkg/querier/tripperware/queryrange/results_cache.go
index 22ccce82d5..21f195b9cc 100644
--- a/pkg/querier/tripperware/queryrange/results_cache.go
+++ b/pkg/querier/tripperware/queryrange/results_cache.go
@@ -265,12 +265,13 @@ func (s resultsCache) shouldCacheResponse(ctx context.Context, req tripperware.R
 	if !s.isAtModifierCachable(ctx, req, maxCacheTime) {
 		return false
 	}
+	if !s.isOffsetCachable(ctx, req) {
+		return false
+	}
 
 	return true
 }
 
-var errAtModifierAfterEnd = errors.New("at modifier after end")
-
 // isAtModifierCachable returns true if the @ modifier result
 // is safe to cache.
 func (s resultsCache) isAtModifierCachable(ctx context.Context, r tripperware.Request, maxCacheTime int64) bool {
@@ -280,6 +281,7 @@ func (s resultsCache) isAtModifierCachable(ctx context.Context, r tripperware.Re
 	//      below maxCacheTime. In such cases if any tenant is intentionally
 	//      playing with old data, we could cache empty result if we look
 	//      beyond query end.
+	var errAtModifierAfterEnd = errors.New("at modifier after end")
 	query := r.GetQuery()
 	if !strings.Contains(query, "@") {
 		return true
@@ -321,6 +323,46 @@ func (s resultsCache) isAtModifierCachable(ctx context.Context, r tripperware.Re
 	return atModCachable
 }
 
+// isOffsetCachable returns true if the offset is positive, result is safe to cache.
+// and false when offset is negative, result is not cached.
+func (s resultsCache) isOffsetCachable(ctx context.Context, r tripperware.Request) bool {
+	var errNegativeOffset = errors.New("negative offset")
+	query := r.GetQuery()
+	if !strings.Contains(query, "offset") {
+		return true
+	}
+	expr, err := parser.ParseExpr(query)
+	if err != nil {
+		level.Warn(util_log.WithContext(ctx, s.logger)).Log("msg", "failed to parse query, considering offset as not cachable", "query", query, "err", err)
+		return false
+	}
+
+	offsetCachable := true
+	parser.Inspect(expr, func(n parser.Node, _ []parser.Node) error {
+		switch e := n.(type) {
+		case *parser.VectorSelector:
+			if e.OriginalOffset < 0 {
+				offsetCachable = false
+				return errNegativeOffset
+			}
+		case *parser.MatrixSelector:
+			offset := e.VectorSelector.(*parser.VectorSelector).OriginalOffset
+			if offset < 0 {
+				offsetCachable = false
+				return errNegativeOffset
+			}
+		case *parser.SubqueryExpr:
+			if e.OriginalOffset < 0 {
+				offsetCachable = false
+				return errNegativeOffset
+			}
+		}
+		return nil
+	})
+
+	return offsetCachable
+}
+
 func getHeaderValuesWithName(r tripperware.Response, headerName string) (headerValues []string) {
 	for name, hv := range r.HTTPHeaders() {
 		if name != headerName {
diff --git a/pkg/querier/tripperware/queryrange/results_cache_test.go b/pkg/querier/tripperware/queryrange/results_cache_test.go
index c6204ac57f..44fc2fe04d 100644
--- a/pkg/querier/tripperware/queryrange/results_cache_test.go
+++ b/pkg/querier/tripperware/queryrange/results_cache_test.go
@@ -416,6 +416,45 @@ func TestShouldCache(t *testing.T) {
 			input:    tripperware.Response(&PrometheusResponse{}),
 			expected: false,
 		},
+		// offset on vector selectors.
+		{
+			name:     "positive offset on vector selector",
+			request:  &PrometheusRequest{Query: "metric offset 10ms", End: 125000},
+			input:    tripperware.Response(&PrometheusResponse{}),
+			expected: true,
+		},
+		{
+			name:     "negative offset on vector selector",
+			request:  &PrometheusRequest{Query: "metric offset -10ms", End: 125000},
+			input:    tripperware.Response(&PrometheusResponse{}),
+			expected: false,
+		},
+		// offset on matrix selectors.
+		{
+			name:     "positive offset on matrix selector",
+			request:  &PrometheusRequest{Query: "rate(metric[5m] offset 10ms)", End: 125000},
+			input:    tripperware.Response(&PrometheusResponse{}),
+			expected: true,
+		},
+		{
+			name:     "negative offset on matrix selector",
+			request:  &PrometheusRequest{Query: "rate(metric[5m] offset -10ms)", End: 125000},
+			input:    tripperware.Response(&PrometheusResponse{}),
+			expected: false,
+		},
+		// offset on subqueries.
+		{
+			name:     "positive offset on subqueries",
+			request:  &PrometheusRequest{Query: "sum_over_time(rate(metric[1m])[10m:1m] offset 10ms)", End: 125000},
+			input:    tripperware.Response(&PrometheusResponse{}),
+			expected: true,
+		},
+		{
+			name:     "negative offset on subqueries",
+			request:  &PrometheusRequest{Query: "sum_over_time(rate(metric[1m])[10m:1m] offset -10ms)", End: 125000},
+			input:    tripperware.Response(&PrometheusResponse{}),
+			expected: false,
+		},
 	} {
 		{
 			t.Run(tc.name, func(t *testing.T) {